Java多线程开发(一)Java多线程编程简介
文章目錄
- 參考
- Java線程簡介
- Thread類構造方法和屬性
- 常用Thread類方法
- 線程的生命周期
- 多線程編程的優勢和風險
- 安全性問題
- 活躍性問題
- 性能問題
參考
【Java并發系列01】Thread及ThreadGroup雜談
Java中interrupt的使用
Java 線程狀態之 RUNNABLE
鎖和監視器之間的區別 – Java并發
Java并發編程實戰:第一章、第十章
Java多線程編程實戰指南(核心篇):第一章、第二章
多線程編程對于現在的開發人員來說是一種很常見的技術了,特別是對于我這樣的Android程序員,“UI操作要在主線程,耗時操作要在子線程“是開始學習Android的時候就要記住的原則。但是多線程代碼要比單線程開發更復雜,還存在一些單線程編程時不會遇到的問題。這篇博客主要是介紹多線程開發的基礎:Thread類的API 和 多線程代碼可能存在的問題。這是我們多線程開發的基礎,下一節我會基于這篇內容,繼續講述Java為了存在的問題而提供給我們的解決工具。
Java線程簡介
對于Java平臺來說,創建一個線程就是創建一個Thread類實例,java.lang.Thread 類是Java平臺對線程的實現。
Thread類構造方法和屬性
Thread有很多的構造方法,最后都會調到同一個初始化方法:private void init(ThreadGroup g, Runnable target, String name, long stackSize),這個方法的參數用于設置Thread的幾個屬性,它們的含義和作用方便是:
- g:ThreadGroup類型, 線程分組,用于對一組線程進行一些批量操作,默認值是父線程(即創建該線程的線程)所屬分組。
- target:Runnable類型, 實現線程的業務邏輯,在run()方法中被調用。
- name:String類型,作為標識,用于打印之類的時候區分線程。默認值格式為Thread-線程編號。
- stackSize:long類型,代表這個線程預期的堆棧大小,不指定默認為0,表示由平臺決定。
除了上面的屬性,線程還有幾個比較重要的屬性
- priority:int類型,這個屬性可以通過setPriority(int newPriority)方法設置,這個屬性是給線程調度器的提示,表示程序希望哪個線程得到更多的運行機會(但不意味著一定能得到)。priority的取值范圍為0~10,默認值是父線程的priority。
- daemon:boolean類型,這個屬性可以通過setDaemon(boolean on)設置(只能在start()方法調用之前調用,即線程運行之前調用,否則會報錯),daemon表示該線程是否為守護線程(守護線程不會阻止虛擬機的關閉),默認值是父線程的daemon。
常用Thread類方法
- static Thread currentThread() :返回當前線程(即當前代碼的執行線程)。
- static void sleep(long millis):使當前線程釋放CPU,休眠一段時間。
- void start():啟動線程(線程啟動的過程是異步的,方法返回不代表新線程已經開始運行),這個方法只能被調用一次。
- void run():用于執行任務邏輯,一般由虛擬機而非應用程序主動調用。
- void interrupt():用于中斷線程,這個方法的效果有兩種情況:
- 如果這個線程正被阻塞,它可以迅速中斷被阻塞的線程,并拋出InterruptedException異常。
- 如果線程沒有被阻塞,它就只是設置一個中斷標志,而不能阻止線程的繼續運行。
- static boolean interrupted():判斷當前線程是否已有中斷標志并清除掉中斷標志(即之后的代碼將不會知道線程曾經被中斷過,除非interrupt()再次調用)。
- boolean isInterrupted():判斷當前線程是否已有中斷標志,這個方法不會清除中斷標志。
線程的生命周期
一個線程在其創建、啟動到運行結束的整個生命周期中會經歷若干狀態,這些狀態由枚舉類Thread.State定義,共有6種,它們分別是:
- NEW:線程創建之后,啟動之前處于這個狀態。因為線程只能被啟動一次,所以線程只會處于這個狀態一次。
- RUNNABLE:這個狀態可以當做兩個子狀態:READY 和RUNNING:
- READY :這個狀態的線程被稱為活躍線程,表示已經準備好由線程調度器調度轉換為RUNNING態。
- RUNNING:表示狀態正在運行。
- BLOCKED:線程被阻塞(發起一個阻塞式I/O操作或者申請被持有的資源時),線程從 RUNNING變為這個狀態,這個狀態下的線程不會占用CPU,當導致線程阻塞的操作完成時,線程重新回到RUNNING態。
- WAITING:線程等待其他線程執行另外特定操作而進入該狀態,同樣會釋放CPU。進入這個狀態的方法有:Object.wait()、Thread.join()、LockSupport,park();對應的喚醒方法是: Object.notify()、Object.notifyAll()、LockSupport.unpark(thread)。
- TIMED_WAITING:和WAITING類型,差別在這個狀態下的線程不會像WAITING態的線程一樣無限制的等下去,在指定時間內沒有等到期待的特定操作時,這個狀態下的線程會自動轉換為RUNNING態。
- TERMINATED:已經結束執行的線程處于這個狀態,和 NEW同理,一個線程也只會處于這個狀態一次。
我看的書上這段說的狀態劃分原則不是很清晰,綜合了一些博客和SDK里Thread類的注釋,我現在的理解是:
- 處于RUNNABLE態的線程區別只在于是否擁有CPU資源:即正在執行和在等待執行,因為CPU會根據時間分片來輪轉調度,所以這兩個子狀態的線程會隨著進出調度隊列而在很短時間內(毫秒級)切換子狀態,從而區分的價值不大。
- BLOCKED和WAITING同樣會暫停執行,同樣會釋放CPU。兩者的區別在于BLOCKED態的線程等待的是監視器鎖,當競爭鎖成功之后,這個線程就會轉換為RUNNABLE態;而 WAITING態的線程是主動暫停運行的,不涉及到鎖,并且需要其他線程調用特定方法來喚醒這個線程,否則這個線程永遠不會執行(TIMED_WAITING則會等待指定時間后醒來)。
線程整個運行和狀態轉換規律如下圖:
多線程編程的優勢和風險
多線程有充分利用CPU、提高系統的吞吐量、更快速響應用戶UI操作等操作等等優勢毋庸贅言,但多線程開發相較于單線程開發也更加復雜,同時也多了很多在單線程開發時不會遇到的問題。下面我總結了多線程帶來的風險。
安全性問題
用《Java多線程編程實戰指南》中的觀點,寬泛地描述多線程的安全性問題就是:
如果一個類在單線程環境下能夠運作正常,并且在多線程環境下,在其使用方不必為其做任何改變的情況下也能運作正常,那么我們就稱其是線程安全(Thread-safe)的,相應地我們稱這個類具有線程安全性(Thread Safety)。反之,如果這個類在單線程環境下運作正常而在多線程環境下則無法正常運作,那么這個類就是非線程安全的。
為什么單線程環境下可以正常運行的代碼放在多線程環境下運行就會出問題呢? 對于同時運行的多個線程來說,如果每個線程的功能和使用的資源都不涉及到其他線程,那么每個線程就是互不影響的,當然不會存在問題,但是這種情況很少。一般而言,多個線程線程之間會存在對某些資源的共享,對應到代碼層面上,就是共享變量的使用。由于Java內存模型和數據緩存的影響,每個線程內部的操作和造成的結果對外部的其他線程都不是立即可見的。由此導致程序出錯。出錯的原因表現為3個方面:原子性、可見性和有序性。
- 原子性:出于我們本意,應該是不可分割的一個操作(這個操作要么沒有執行,要么執行了)被其他線程在其中插入了其他操作,導致狀態錯誤。
- 可見性:一個線程更新了狀態不能被另一個線程立即看到,導致另一個線程讀取到了舊狀態或者重復更新覆蓋掉了這次更新。
- 有序性:出于優化的目的,編譯器和處理器會對代碼指令調整順序,這種調整的規則只保證單線程內是正確的,而不考慮多線程。比如類a進行操作A,C;類b進行操作依賴于A的D,于是b通過判斷a是否已經完成了C來決定是否進行操作D,單線程下這個思路是正確的,但多線程下,指令重排序下a的操作A,C的順序就可能發生改變,導致C操作完成于A之前,以至于D操作失敗。
活躍性問題
安全性問題是保證“程序運行時不會發生錯誤”,而活躍性則關注的是“程序會正常的結束運行”。由于缺少某些資源或程序本身缺陷導致程序無法繼續下一步操作時,就是發生了活躍性問題,單線程環境下,程序進行無限循環就是一種問題的形式,而在多線程環境下,活性問題大多由線程交互導致:
- 死鎖:兩個線程同時以不同的順序來獲得不同鎖,導致各自持有一個鎖,而請求對方持有的鎖,導致程序無法繼續運行。
- 鎖死:如果有一個線程調用了wait方法,但是一直沒有其他線程調用notify方法或者是在它調用wait之前調用了notify導致這個線程收不到喚醒信號而一直掛起,這種情況就被稱為鎖死。
- 饑餓:修改某個線程優先級導致這個線程一直得不到CPU調用。
- 活鎖:線程申請資源一直不成功,或者其他原因,導致線程一直重復執行相同的操作。
性能問題
與活躍性問題密切相關的是性能問題。活躍性意味著程序會正常運行得到結果,但是不保證效率,因為我們還希望程序的性能,即運行的速度越快越好。多線程編程的性能過,可能由很多原因導致:
- 頻繁的切換線程導致的上下文切換,導致CPU花費過多時間用于線程調度。
- 多個線程共享數據為了線程安全必須使用同步機制,從而帶了額外的性能開銷。
總結
以上是生活随笔為你收集整理的Java多线程开发(一)Java多线程编程简介的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 车联网V-2X智能汽车驾驶
- 下一篇: Java大数乘法