1.Java線程狀態
1.1 線程主要狀態
①初始(NEW):新創建了一個線程對象,但還沒有調用start()方法。
②運行(RUNNABLE):Java線程中將就緒(ready)和運行中(running)兩種狀態籠統的成為“運行”。
線程對象創建后,其他線程(比如main線程)調用了該對象的start()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取cpu 的使用權,此時處於就緒狀態(ready)。就緒狀態的線程在獲得cpu 時間片后變為運行中狀態(running)。
③阻塞(BLOCKED):表線程阻塞於鎖。
④等待(WAITING):進入該狀態的線程需要等待其他線程做出一些特定動作(通知或中斷)。
⑤超時等待(TIME_WAITING):該狀態不同於WAITING,它可以在指定的時間內自行返回。
⑥終止(TERMINATED):表示該線程已經執行完畢。
1.2 線程狀態切換
示意圖如下:
1.2.1 初始狀態
- 實現Runnable接口和繼承Thread可以得到一個線程類,new一個實例出來,線程就進入了初始狀態
1.2.2 就緒狀態
- 就緒狀態只是說你資格運行,調度程序沒有挑選到你,你就永遠是就緒狀態。
- 調用線程的start()方法,此線程進入就緒狀態。
- 當前線程sleep()方法結束,其他線程join()結束,等待用戶輸入完畢,某個線程拿到對象鎖,這些線程也將進入就緒狀態。
- 當前線程時間片用完了,調用當前線程的yield()方法,當前線程進入就緒狀態。
- 鎖池里的線程拿到對象鎖后,進入就緒狀態。
1.2.3 運行中狀態
- 線程調度程序從可運行池中選擇一個線程作為當前線程時線程所處的狀態。這也是線程進入運行狀態的唯一一種方式。
1.2.4 阻塞狀態
- 阻塞狀態是線程阻塞在進入synchronized關鍵字修飾的方法或代碼塊(獲取鎖)時的狀態。
1.2.5 終止狀態
- 當線程的run()方法完成時,或者主線程的main()方法完成時,我們就認為它終止了。這個線程對象也許是活的,但是,它已經不是一個單獨執行的線程。線程一旦終止了,就不能復生。
- 在一個終止的線程上調用start()方法,會拋出java.lang.IllegalThreadStateException異常。
1.2.6 等待隊列(本是Object里的方法,但影響了線程)
- 調用obj的wait(), notify()方法前,必須獲得obj鎖,也就是必須寫在synchronized(obj) 代碼段內。
- 與等待隊列相關的步驟和圖
- 1.線程1獲取對象A的鎖,正在使用對象A。
- 2.線程1調用對象A的wait()方法。
- 3.線程1釋放對象A的鎖,並馬上進入等待隊列。
- 4.鎖池里面的對象爭搶對象A的鎖。
- 5.線程5獲得對象A的鎖,進入synchronized塊,使用對象A。
- 6.線程5調用對象A的notifyAll()方法,喚醒所有線程,所有線程進入同步隊列。若線程5調用對象A的notify()方法,則喚醒一個線程,不知道會喚醒誰,被喚醒的那個線程進入同步隊列。
- 7.notifyAll()方法所在synchronized結束,線程5釋放對象A的鎖。
- 8.同步隊列的線程爭搶對象鎖,但線程1什么時候能搶到就不知道了。
注意:等待隊列里許許多多的線程都wait()在一個對象上,此時某一線程調用了對象的notify()方法,那喚醒的到底是哪個線程?隨機?隊列FIFO?or sth else?java文檔就簡單的寫了句:選擇是任意性的(The choice is arbitrary and occurs at the discretion of the implementation)。
1.2.7 同步隊列狀態
- 當前線程想調用對象A的同步方法時,發現對象A的鎖被別的線程占有,此時當前線程進入同步隊列。簡言之,同步隊列里面放的都是想爭奪對象鎖的線程。
- 當一個線程1被另外一個線程2喚醒時,1線程進入同步隊列,去爭奪對象鎖。
- 同步隊列是在同步的環境下才有的概念,一個對象對應一個同步隊列。
2.線程的核心方法
- Thread.sleep(long millis),一定是當前線程調用此方法,當前線程進入TIME_WAITING狀態,但不釋放對象鎖,millis后線程自動蘇醒進入就緒狀態。作用:給其它線程執行機會的最佳方式。
- Thread.yield(),一定是當前線程調用此方法,當前線程放棄獲取的cpu時間片,由運行狀態變會就緒狀態,讓OS再次選擇線程。作用:讓相同優先級的線程輪流執行,但並不保證一定會輪流執行。實際中無法保證yield()達到讓步目的,因為讓步的線程還有可能被線程調度程序再次選中。Thread.yield()不會導致阻塞。
- t.join()/t.join(long millis),當前線程里調用其它線程t的join方法,當前線程進入TIME_WAITING/TIME_WAITING狀態,當前線程不釋放已經持有的對象鎖。線程t執行完畢或者millis時間到,當前線程進入就緒狀態。
- obj.wait(),當前線程調用對象的wait()方法,當前線程釋放對象鎖,進入等待隊列。依靠notify()/notifyAll()喚醒或者wait(long timeout)timeout時間到自動喚醒。
- obj.notify()喚醒在此對象監視器上等待的單個線程,選擇是任意性的。notifyAll()喚醒在此對象監視器上等待的所有線程。
2.1 wait/notify/notifyAll方法詳細介紹
wait()和notify()一系列的方法,是屬於對象的,不是屬於線程的。它們用在線程同步時,synchronized語句塊中。
我們都知道,在synchronized語句塊中,同一個對象,一個線程在執行完這一塊代碼之前,另一個線程,如果傳進來的是同一個object,是不能進入這個語句塊的。也就是說,同一個對象是不能同時被兩個線程用來進入synchronized中的。這就是線程同步。
wait()意思是說,我等會兒再用這把鎖,CPU也讓給你們,我先休息一會兒!
notify()意思是說,我用完了,你們誰用?
也就是說,wait()會讓出對象鎖,同時,當前線程休眠,等待被喚醒,如果不被喚醒,就一直等在那兒。notify()並不會讓當前線程休眠,但會喚醒休眠的線程。
先看第一個例子!
public class ThreadTest { public static void main(String[] args) { final Object object = new Object(); Thread t1 = new Thread() { public void run() { synchronized (object) { System.out.println("T1 start!"); try { object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("T1 end!"); } } }; Thread t2 = new Thread() { public void run() { synchronized (object) { System.out.println("T2 start!"); object.notify(); System.out.println("T2 end!"); } } }; t1.start(); t2.start(); } }
這第一個例子很簡單,寫了兩個線程(分別是兩個類,兩個run方法)。兩個run方法之間沒有關系,但是,他們都用了同一個object!仔細看,T1里面主要寫了個wait(),而T2里面主要寫了個notify()。我們看到執行結果是:
T1 start!
T2 start!
T2 end!
T1 end!
流程可以這樣解釋:
T1啟動,讓出鎖,讓出CPU,T2獲得CPU,啟動,喚醒使用了object的休眠的線程,T1被喚醒后等待啟動,T2繼續執行,T2執行完,T1獲得CPU后繼續執行。
值得一提的是,再強調一遍:
wait會讓出CPU而notify不會,notify重在於通知使用object的對象“我用完了!”,wait重在通知其它同用一個object的線程“我暫時不用了”並且讓出CPUT。
所以說,看上面的順序,
T2 start!
T2 end!
是連續的,說明它並沒有因調用了notify而暫停!
那么,如果兩個線程都寫wait沒有線程寫notify會有什么現象呢?試一下就知道了。
結果是,
T1 start!
T2 start!
然后就是一直等待!
道理很顯然,T1先啟動,然后wait了,T2獲得了鎖和CPU,在沒有其它線程與它競爭的情況下,T2執行了,然后也wait了。
在這里,兩個線程都在等待,沒有其它線程將它們notify,所以結果就是無休止地等下去!
至少說明了一點,wait后如果沒有其它線程將它notify,是絕不可能重新啟動的。不可能因為目前沒有線程占用CPU,某一個正在等待的線程就自動重啟。
下面,我再把它改一下,寫四個線程,分別是
T1 wait()
T2 notify()
T3 notify()
T4 wait()
public class ThreadF {
public static void main(String[] args) {
final Object object = new Object();
Thread t1 = new Thread() {
public void run()
{
synchronized (object) {
System.out.println("T1 start!");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T1 end!");
}
}
};
Thread t2 = new Thread() {
public void run()
{
synchronized (object) {
System.out.println("T2 start!");
object.notify();
System.out.println("T2 end!");
}
}
};
Thread t3 = new Thread() {
public void run()
{
synchronized (object) {
System.out.println("T3 start!");
object.notify();
System.out.println("T3 end!");
}
}
};
Thread t4 = new Thread() {
public void run()
{
synchronized (object) {
System.out.println("T4 start!");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T4 end!");
}
}
};
t1.start();
t2.start();
t3.start();
t4.start();
}
}
首先,大家知道,線程啟動的順序,和代碼的先后順序,理論上是沒有關系的!
比如我這兒寫的是按T1-T2-T3-T4的先后順序先后start(),但實際上誰先啟動,是有一定幾率的。
執行上面代碼,有兩種結果:
一種是剛好wait兩次,notify兩次,notify在wait之后執行,剛好執行完。
另一種是,也是剛好wait兩次,notify兩次,但是,notify在wait之前執行,於是,至少會有一個線程由於后面沒有線程將它notify而無休止地等待下去!
我摘選了兩種情況的輸出結果,僅供參考:
1、可以執行結束的情況:
T1 start!
T2 start!
T2 end!
T1 end!
T4 start!
T3 start!
T3 end!
T4 end!
執行流程是:
T1啟動,wait,T2獲得鎖和CPU,T2宣布鎖用完了其它線程可以用了,然后繼續執行,T2執行完,T1被剛才T2喚醒后,等待T2執行完后,搶到了CPU,T1執行,
T1執行完,T4獲得CPU,啟動,wait,T3獲得了鎖和CPU,執行,宣布鎖用完了,其它線程可以用了,然后繼續執行,T3執行完,已經被喚醒並且等待已久的T4
得到CPU,執行。
2、不能執行結束,有線程由於沒有其它線程喚醒,一直在等待中:
T1 start!
T2 start!
T2 end!
T1 end!
T3 start!
T3 end!
T4 start!
執行流程:
T1啟動,wait,讓出CPU和鎖,T2得以啟動。T2啟動,並喚醒一個線程,自己繼續執行。被喚醒的線程,也就是T1等待啟動機會。
T2執行完,T1搶到了CPU,執行,並結束。
這時,只剩下T3和T4,在此時,兩個線程的機會均等。
但是,T3搶到了CPU,於是它執行了,而且喚醒了線程,雖然此時並沒有線程休眠。說白了,它浪費了一次notify。T3順利執行完。
這時,終於輪到了T4,它啟動了,wait了,但是,后面已經沒有線程了,它的wait永遠不會有線程幫它notify了!
於是,它就這么等着!
順便說一下,如果沒有線程在wait,調用notify是不會有什么問題的,就像這樣:
public class ThreadG {
public static void main(String[] args) {
final Object object = new Object();
Thread t1 = new Thread() {
public void run()
{
synchronized (object) {
System.out.println("T1 start!");
object.notify();
System.out.println("T1 end!");
}
}
};
t1.start();
}
}
T1 start!
T1 end!
引用:
https://blog.csdn.net/pange1991/article/details/53860651/
https://blog.csdn.net/superit401/article/details/52254087