一:等待/通知機制
線程與線程之間不是獨立的個體,它們之間可以互相通信和協作。
1: 不需要等待/通知機制實作線程間的通信:
2:等待/通知機制
多個線程之間可以實作通信,原因是多個線程共同通路同一個變量,但這種機制不是“等待/通知”,兩個線程完全是主動地去讀取一個共享變量,在花費讀取時間的基礎上,讀到的值不是想要的,不能完全确定。是以需要一種“等待/通知”機制來滿足需求。
3:等待/通知機制的實作
方法wait()的作用是使目前執行代碼的線程進行等待,wait()是Object類的方法,該方法用來将目前線程置于“預執行隊列”中,并且wait()所在的代碼行處停止執行 ,直到通知被中斷為止。
在調用wait()之前,線程必須獲得該對象的對象級别鎖,即隻能在同步方法或者同步塊中調用wait()方法。
在執行wait()方法後,目前線程釋放鎖,抛出IllegalMonitorStateException,不需try-catch語句進行捕獲異常。
方法notify()也需在同步方法或者同步塊中調用,即在調用前,線程也必須獲得該對象的對象級别鎖。如果在調用notify()時沒有持有适當的鎖,也會抛出IllegalMonitorStateException。
該方法用來通知那些可能等待該對象的對象鎖的其他線程,如果有多個線程等待,則由線程規劃器随機挑出其中一個呈wait狀态的線程,對其發出通知notify,并使其等待擷取該對象的對象鎖。
執行notify()方法後,目前線程不會馬上釋放該對象鎖,呈wait狀态的線程也也并不能馬上擷取該對象鎖,要等到notify()方法的線程将程式執行完,也就是出synchronized代碼塊後,目前線程才會釋放鎖,而呈wait狀态所在的線程才可以擷取該對象鎖。當第一次獲得該對象鎖的wait線程運作完畢以後,它會釋放該對象鎖,如果此時該對象沒有再次使用notify語句,則即該對象已經空閑,其他wait狀态等待的線程由于沒有得到該對象的通知,還會繼續阻塞在wait狀态,直到這個對象發出一個notify或notifyAll。(即notify方法一次隻能喚醒一個鎖)
wait使線程停止運作,而notify使停止的線程繼續執行。
關鍵字synchronized可以将任何一個Object對象作為同步對象來看待,Java為每個對象都實作了wait()和notify()方法,它們必須用在被synchronized同步的Object的臨界區内。
wait()方法可以使調用該方法的線程釋放共享資源的鎖,然後從運作狀态退出,進入等待隊列,直到被再次喚醒。
notify()方法可以随機喚醒等待隊列中等待同一共享資源的“一個”線程”,并使該線程退出等待隊列,進入可運作狀态,這就是notify()方法僅通知“一個”線程。
notifyAll()方法可以使所有正在等待隊列中等待同一資源的“全部”線程從等待狀态退出,進入可運作狀态。優先級高的那個線程最先執行,但也有可能随機執行,則取決于JVM虛拟機。
4:方法wait()鎖釋放與notify()鎖不釋放
當方法wait()被執行後,鎖被自動釋放,但執行完notify()方法,鎖卻不會馬上釋放。
sleep()方法不釋放鎖。
目前對象線程(Lock)在同步代碼塊中執行notify(),線程對象(Lock)不會馬上釋放鎖,而是必須執行完notify()方法所在的同步synchronized代碼塊後才釋放鎖。
5:當interrupt方法遇到wait()方法
當線程呈wait()狀态時,調用線程對象的interrupt()方法會出現InterruptedException異常。
但是wait()和sleep()都可以通過interrupt()方法打斷線程的暫停狀态,進而使線程立刻抛出InterruptedException。
如果線程A希望立即結束線程B,則可以對線程B對應的Thread執行個體調用interrupt方法。如果此刻線程B正在wait/sleep/join,則線程B會立刻抛出InterruptedException,在catch() {} 中直接return即可安全地結束線程。
需要注意的是,InterruptedException是線程自己從内部抛出的,并不是interrupt()方法抛出的。對某一線程調用interrupt()時,如果該線程正在執行普通的代碼,那麼該線程根本就不會抛出InterruptedException。但是,一旦該線程進入到wait()/sleep()/join()後,就會立刻抛出InterruptedException。
Thread.interrupt() VS Thread.stop()
這兩個方法最大的差別在于:interrupt()方法是設定線程的中斷狀态,讓使用者自己選擇時間地點去結束線程;而stop()方法會在代碼的運作處直接抛出一個ThreadDeath錯誤,這是一個java.lang.Error的子類。是以直接使用stop()方法就有可能造成對象的不一緻性。
6:隻通知一個線程
方法notify()僅随機喚醒一個線程,
在多次調用notify()方法時,會随機将等待wait狀态的線程進行喚醒。多次調用notify()方法會喚醒全部WAITING中的線程
7:喚醒全部線程
notifyAll()方法會喚醒全部線程。
8:方法wait(long)的使用
帶參數的wait(long)方法的功能是将等待某一時間内是否有線程對鎖進行喚醒,如果逾時則自動喚醒。
9:通知過早
如果先通知notify()方法 thread.sleep(),則wait()方法後的代碼即不會被執行。(等待被喚醒---》方法wait永遠不會被通知)或者notify()方法改變了對象的值,使其wait()方法的同步代碼塊不符合條件,則不執行。
如果先wait()方法 thread.sleep(),則notify()方法執行完synchronized代碼塊後的代碼,再去執行wait()後的代碼。
10:等待wait的條件發生變化
在使用wait()/notify()模式時,如果wait()的條件發生變化,則會造成程式邏輯的混亂。
用while()隻要條件不成立就不會執行,少用 i f { }判斷。
if 與 while判斷條件的合理使用
11:生産者/消費者模式實作
等待/通知模式最經典案例就是“生産者/消費者”模式。其原理都是基于wait/notify。
1)一生産與一消費:操作值
建立兩個線程對象,一個生産者線程,一個消費者線程。
2)多生産與多消費:操作值---假死
線程全部進入WAITING狀态,則程式就不會再執行業務功能了,整個項目呈停止狀态。即“假死”。
呈假死狀态的程序中所有的線程都呈WAITING狀态。
代碼中通過wait/notify進行通信,但不能保證notify喚醒的是異類或者同類。則線程不能繼續運作下去,大家都在等待,則呈WAITING狀态。
3)多生産與多消費者:操作值
notify()改為notifyAll()通知所有類,防止“假死”狀态。
4)一生産與一消費:操作棧
生産者使用堆棧List對象中放入資料,消費者從List堆棧中取出資料,List容量為1。
5)一生産與多消費---操作棧:解決wait條件改變與假死
使用一個生産者向堆棧List對象中放入資料,而多個消費者從List堆棧中取出資料,List容量為1。
6)多生産與一消費:操作棧
使用生産者向堆棧List對象中放入資料,而多個消費者從List堆棧中取出資料,List容量為1。
7)多生産與多消費:操作棧
使用生産者向堆棧List對象中放入資料,而多個消費者從List堆棧中取出資料,List容量為1。
12:通過通道進行線程間的通信:位元組流
管道流:用在不同線程間直接傳輸資料,實作不同線程間的通信。
1)PipedInputStream和PipedOutputStream
PipedInputStream inputStream=new PipedInputStream()
PipedOutputStream outputStream=new PipedOutputStream()
讀取線程new ThreadRead(outputStream)啟動。使用inputStream。connect(outputStream)或outputStream(inputStream)實作兩個Stream之間的通信連接配接。
13:通過通道進行線程間的通信:字元流
1)PipedReader和PipedWriter
14:等待/通知之交叉備份
原理:volatile private Boolean prevIsA=false;
二:方法join的使用
很多情況下,主線程建立并啟動子線程,如果子線程中要進行大量的耗時運算,主線程往往早于子線程結束。如果主線程需要子線程的資料則需要join()方法。 方法join的作用是使所屬的線程對象 x 正常執行run()方法中的任務,而使目前線程 z 進行無限制的阻塞,等待線程 x 銷毀後再繼續執行 z 後面的代碼。
方法join()的作用是等待線程對象的銷毀。方法join具有使線程排隊運作的作用,類似于同步的運作效果。
join與synchronized的差別:
join在内部使用wait()方法進行等待,而synchronized關鍵字使用的是“對象螢幕”原理作為同步。
在join過程中,如果目前線程對象被中斷,則目前線程出現異常,
方法join(long)設定等待的時間
join()與sleep()方法對同步處理上的差別:
方法join(long)的功能在内部使用wait(long)方法來實作的,是以方法join()方法具有釋放鎖的特點。而sleep(long)方法不釋放鎖。
方法join()後面的代碼提前運作:出現意外。
三:類ThreadLocal的使用
public static 變量的形式可以實作變量值的共享, 所有的線程可以使用同一個public static 變量。
類ThreadLocal實作每一個線程都有自己的共享變量。
類ThreadLocal主要解決的是使每個線程綁定自己的值,可以将ThreadLocal類比喻為全局存放資料的盒子,盒子中可以存儲每個線程的私有資料。
類ThradLocal解決的是變量在不同線程間的隔離性,也就是不同線程擁有自己的值。及使每個線程都有自己的私有屬性。
get()傳回null 問題———》讓實作類繼承ThreadLocal類,并覆寫initialValue()方法,使其方法具有初始值,傳回初始值。
子線程和父線程各有各自所擁有的的值,不存在繼承關系。
四:類InheritableThreadLocal的使用
使用類 InheritableThreadLocal可以在子線程中取得父線程繼承下來的值。
注意:如果使用InheritableThreadLocal類,字線程在取得值的同時,父線程将InheritableThreadLocal中的值進行更改,那麼子線程的值還是舊值不會改變。
以下文章來自:http://blog.csdn.net/linxdcn/article/details/72819817?fps=1&locationNum=13
線程狀态
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
- NEW 狀态是指線程剛建立, 尚未啟動
- RUNNABLE 狀态是線程正在正常運作中, 當然可能會有某種耗時計算/IO等待的操作/CPU時間片切換等, 這個狀态下發生的等待一般是其他系統資源, 而不是鎖, Sleep等
- BLOCKED 這個狀态下, 是在多個線程有同步操作的場景, 這個事件将在另一個線程放棄了這個鎖的時候發生,也就是這裡是線程在等待進入臨界區
- WAITING(無線等待) 這個狀态下是指線程擁有了某個鎖之後, 調用了他的wait方法, 等待其他線程/鎖擁有者調用 notify / notifyAll 一遍該線程可以繼續下一步操作, 這裡要區分 BLOCKED 和 WATING 的差別, 一個是在臨界點外面等待進入, 一個是在臨界點裡面wait等待别人notify, 線程調用了join方法 join了另外的線程的時候, 也會進入WAITING狀态, 等待被他join的線程執行結束
- TIMED_WAITING 這個狀态就是有限的(時間限制)的WAITING, 一般出現在調用wait(long), join(long)等情況下, 另外一個線程sleep後, 也會進入TIMED_WAITING狀态
- TERMINATED 這個狀态下表示 該線程的run方法已經執行完畢了, 基本上就等于死亡了(當時如果線程被持久持有, 可能不會被回收)
java層次的狀态轉換圖
作業系統層次的狀态轉換圖
(2)wait()和notify/notifyAll()方法
wait()方法
- 線程進入WAITING狀态,并且釋放掉它所占有的“鎖标志”,進而使别的線程有機會搶占該鎖,等待其他線程調用“鎖标志“對象的notify或notifyAll方法恢複
- wait方法是一個本地方法,其底層是通過一個叫做螢幕鎖的對象來完成的,是以調用wait方式時必須擷取到monitor對象的所有權即通過Synchronized關鍵字,否則抛出IllegalMonitorStateException異常
notify/notifyAll()方法
- 在同一對象上去調用notify/notifyAll方法,就可以喚醒對應對象monitor上等待的線程了。notify和notifyAll的差別在于前者隻能喚醒monitor上的一個線程,對其他線程沒有影響,而notifyAll則喚醒所有的線程
(3)sleep/yield/join方法解析
sleep
- sleep方法的作用是讓目前線程暫停指定的時間(毫秒)
- wait方法依賴于同步,而sleep方法可以直接調用
- sleep方法隻是暫時讓出CPU的執行權,并不釋放鎖。而wait方法則需要釋放鎖
yield
- yield方法的作用是暫停目前線程,以便其他線程有機會執行,不過不能指定暫停的時間,并且也不能保證目前線程馬上停止
- yield隻能使同優先級或更高優先級的線程有執行的機會
join
- 等待調用join方法的線程結束,再繼續執行。如:t.join(),主要用于等待t線程運作結束
- 作用是父線程等待子線程執行完成後再執行,換句話說就是将異步執行的線程合并為同步的線程
(4)不推薦使用方法解釋
參考:Why Are Thread.stop, Thread.suspend, Thread.resume and Runtime.runFinalizersOnExit Deprecated?
suspend()和resume()
- 這兩個方法是配套使用的,suspend()是暫停線程,但并不釋放資源,容易造成死鎖情況
stop()
- 因為調用stop會使線程釋放所有的鎖,導緻不安全情況,在調用stop時候,由鎖保護的臨界區可能處于狀态不一緻的情況,這不一緻狀态将暴露給其他線程
- 推薦的做法是,維護一個狀态變量,當線程需要停止時更改這一狀态變量,該線程應檢查這一狀态變量,看該線程是否應該終止了
(5)關于interrupt()中斷函數
- 其實調用這個函數并不是真的中斷線程,這個函數隻是将Thread中的interrupt标志設定為true,使用者需自行檢測這一變量,停止線程,這種做法避免了stop帶來的問題
推薦:http://blog.csdn.net/zolalad/article/details/38903911