Java多執行緒程式設計寫法
寫在開頭
前段時間看了些java多執行緒的書和博文,但是在接下來倒沒有太多用到,為了防止遺忘,準備總結一篇博文記錄一下。注:此文只能作為複習使用,如果想要系統學習多執行緒程式設計請購買相關書籍如《Java多執行緒程式設計核心技術》等或參考更詳細博文。
多執行緒的寫法
多執行緒的實現方式大致有如下兩種:
- 繼承Thread類:extend Thread 實現 run 函式
- 實現Runnable介面:implements Runnable 實現run函式
執行緒安全
通過synchronized關鍵字實現執行緒間共享變數的資料安全。
Thread.currentThread()可以獲取當前執行緒例項。
執行緒排程
執行緒等待:使用sleep函式使執行緒等待相應時間
執行緒中止:使用推出標誌、使用stop(已廢棄的方法不推薦,可能產生無法預期的後果 如:會釋放鎖讓資料不一致)、使用interrupt(軟中斷,需要在run函式中判斷isInterrupted是否已中斷)
執行緒暫停:suspend暫停執行緒 resume恢復執行緒,但是這種使用方式有如下兩個缺點:1-暫停執行緒中的同步物件加鎖後suspend會導致鎖無法釋放,其他執行緒飢餓等待。2-暫停執行緒中如果有傳入物件進行資料操作,可能會造成只操作一半,資料不同步。
另外,yield方法也可以將執行緒暫停由其他的執行緒獲取CPU資源。
執行緒優先順序:setPriority可以設定執行緒優先順序,執行緒優先順序具有繼承性、規則性及隨機性。
多執行緒物件及併發訪問
例項變數非執行緒安全:多個執行緒共享一個例項變數時可能會出現非執行緒安全,可以給變數或變數方法加同步鎖(synchronized)
Synchronized不僅可以給方法加鎖也可以單獨給語句塊加鎖來提升執行緒執行效率。
synchronized(this){ //coding }
synchronized同步程式碼塊會將括號中的物件作為物件監視器,在物件相同的程式碼塊會呈現同步效果,也會與該物件的同步方法呈現同步效果。
需要注意的是synchronized關鍵字加在static靜態方法時,持有的Class鎖,會和持有該class例項化物件鎖的程式碼呈現同步效果。
synchronized以string為鎖物件的時候需要注意string常量池的問題,相同值的string持有一把鎖,所以一般不建議使用string作為鎖物件。
volatile關鍵字
volatile是執行緒同步的輕量實現,效能上優於synchronized,且在多執行緒訪問時也不會造成阻塞,volatile可以保證多執行緒的資料可見性,但不能保證原子性,而synchronized可以同時保證原子性和可見性,另外volatile只能修飾變數。
volatile的主要使用場合是在多執行緒可以感知例項變數被更改了,並且可以獲得最新的值使用。
原子類
使用原子類(如:AtomicInt)可以保證其方法的原子性,但是方法和方法之間的呼叫不是原子的。所以還是需要在程式碼中使用synchronized。
執行緒間通訊
執行緒(A)可以使用持有物件鎖的wait()方法,此時會釋放該物件的鎖,然後等待其他執行緒(B)呼叫該物件的notify()方法喚起此執行緒(A)繼續執行之後的程式碼,另外notify會隨機喚醒一個此物件wait的執行緒,而notifyAll則喚醒全部。
需要注意的是notify並不會釋放鎖,當前notify執行緒會繼續執行直到釋放物件鎖後,被喚起的執行緒才會開始執行。
wait也可以使用long型引數作為超時自動喚醒的引數,只要當時該物件鎖未被佔用,則執行緒自動喚醒。
使用執行緒(A)的join方法可以使當前執行緒等待該執行緒(A)完成run之後開始執行,另外的join也可以傳入long引數作為超時自動喚醒的引數。join的內部實現是基於wait的,所以join也會釋放鎖。
前面提到了sleep也會阻塞執行緒使其等待,但是不同的是sleep不會釋放鎖。
執行緒共享變數
類ThreadLocal可以在不同執行緒中實現各執行緒隔離的共享變數,不同執行緒的ThreadLocal的值將保持隔離、不會汙染、父子執行緒也不會繼承。
而類InheritableThreadLocal可以在子執行緒取得父執行緒繼承下的值,但是在繼承之後子執行緒與父執行緒仍會保持隔離。
Lock的使用
除了使用synchronized關鍵字實現同步外,Java1.5新增了ReentrantLock也可以達成同步的效果。synchronized可以使用wait/notify進行執行緒間通訊。ReentranLock也提供了通訊類Condition,該類可以使用await/signal/signalAll進行等待和喚醒,另外因為ReentranLock可以建立多個Condition,所以也可以實現點對點多路通知。同樣的,await需要先持有物件鎖進行lock。
以下簡單程式碼示例:
public class Service { private Lock lock = new ReentrantLock(); public Condition conditionA = lock.newCondition(); public Condition conditionB = lock.newCondition(); public void waitA(){ lock.lock(); conditionA.await(); lock.unlock(); } public void signalA(){ lock.lock(); conditionA.signal(); lock.unlock(); } }
另外ReentrantLock有以下幾種方法:
- getHoldCount() 查詢當前執行緒鎖定的個數
- getQueueLength() 返回正在等待獲得該鎖定的執行緒數
- getWaitQueueLength(Condition c)返回等待該鎖定此條件(c)的執行緒數
- hasQueueThread(Thread t)查詢指定執行緒是否正在等待該鎖定
-
hasWaiters(Condition c)查詢是否有執行緒等待該鎖定此條件(c)
剩餘不再列舉
相對於完全互斥的ReentrantLock,ReentrantReadWriteLock在某些不需要操作例項變數的方法中,可以使用讀寫鎖(readlock/writelock)來加快執行效率。讀鎖共享、寫鎖與其他鎖互斥。
定時器
Timer使用schedule進行定時操作,第一個引數一般傳入具體的timertask。其餘可以接受task執行的具體日期時間Datetime或者延遲執行時間long,也可以傳入間隔時間period(long型),以間隔時間迴圈執行task。
另外timertask的cancel方法可以取消當前task的執行,而timer的cancel將清除所有的任務佇列。
相對於schedule,還有scheduleAtFixedRate該方法基於上次任務結束後的時間開始進行迴圈。
寫在結尾
多執行緒是一門值得深入研究和探討的核心技術,以上總結寫得比較簡單淺薄,如有遺漏請不吝提醒我,基本參考自《Java多執行緒程式設計核心技術》,非常感謝前輩們貢獻知識。