摘要:通知任一一個進入等待狀態的線程,通知所有讓調用線程阻塞在這個方法上,直到的線程完全執行完畢,調用線程才會繼續執行。通知調度器,主動讓出對的占用。
多線程在開發知識中是一個很重要的部分,然而實際生產中卻很少遇到真正需要自己去處理多線程編程里的那些復雜細節和問題,因為很多時候,都有一套“架構”或者一些“框架”幫大部分業務程序員隱藏了多線程的細節,大多時候只需要簡單的實現各種業務邏輯即可。
今天來理一理wait, notify, join, yield這四個方法的作用。
這4個方法,其中wait, notify都是Object的方法,join是Thread的實例方法,yield是Thread的靜態方法。
wait, notify在之前的文章:xxxx中我已經提到過,wait將線程轉換為Waiting狀態,notify喚醒一個在Waiting狀態的線程。
咱們一個個來說。
Object.wait文檔上是這樣描述的:
Causes the current thread to wait until either another thread invokes the Object#notify() method or the Object#notifyAll() method for this object, or a specified amount of time has elapsed.
它是說:導致當前線程進入waiting,直到另一個線程調用notify或者notifyAll方法來喚醒它,或者是指定了等待時間。
也就是用wait的有參的重載方法wait(long),可以讓線程至多等待一定的時間,這個時間過了之后,線程就自行恢復runnable狀態了。
正確的使用方法是在synchronized里面使用,并且使用一個循環將它包起來。
synchronized (lock) { while (!condition) { lock.wait() // 進入 waiting 狀態, 這行代碼之后的代碼將不會被執行 } }
為什么要使用一個循環呢?因為通常情況下,按照邏輯的要求是達到某種條件之前,我這個線程就不工作了,當條件滿足后,別的線程來通知我,當別的線程通知我之后呢,我還要再check一下這個條件是否滿足,如果不滿足,還要繼續進入waiting狀態,這樣邏輯上才是比較完備的。
Object.notifyWakes up a single thread that is waiting on this object"s monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation. A thread waits on an object"s monitor by calling one of the {@code wait} methods.
喚醒一個waiting態的線程,這個線程呢,必須是用同一把鎖進入waiting態的。
所以,notify方法的通常使用方法為:
synchronized (lock) { lock.notify() }
有人可能要問了:wait和notify不在synchronized里面使用會怎么樣?
我也好奇了這一點,然后實驗了一把,發現會拋異常,運行時直接報錯
java.lang.IllegalMonitorStateException at java.lang.Object.wait(Native Method)Thread.join
join方法是一個實例方法,先看看文檔的定義:
//Waits for this thread to die. public final void join() throws InterruptedException
它的意思是,調用threadA.join()的線程,要進入waiting狀態,一直到線程threadA執行完畢。
比如
public static void main() { Thread t1 = new Thread(…); t1.join(); // 這行代碼必須要等t1全部執行完畢,才會執行 }Thread.yield
A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore
this hint. Yield is a heuristic attempt to improve relative progression between threads that would otherwise over-utilize a CPU.
Its use should be combined with detailed profiling and benchmarking to ensure that it actually has the desired effect.public static native void yield();
這個方法的意思是,告訴調度器,當前線程愿意放棄cpu的使用,愿意將cpu讓給其它的線程。
用人話說就是:哎呀,我現在已經運行了那么久了,把機會留給別人吧,cpu你快去運行一下其他線程吧,我歇一會。
但是按文檔上的描述,這只是對調度器的一個暗示。也就是說,具體會發生什么,還要看調度器是如何處理的。
所以我又來捏造需求了。我們先看看下面的代碼會發生什么:
public static void main(String[] arg) { Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10000; i++) { count++; } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10000; i++) { count++; } } }); t1.start(); t2.start(); }
兩個線程一起給count自增10000次,由于沒有加鎖,和自增也不是一個原子操作,這樣就會導致,兩個線程都自增10000次,最后count的結果,一定是小于20000的一個數。
等等!等一下!自增不是原子操作是怎么回事,這代碼不是只有一行嗎?
大家都知道代碼最終會被翻譯為指令,由cpu去執行,一條指令是原子的,但是一行代碼被翻譯成多條指令,那么也就會被多個線程交替進行,這也就是多線程編程常見的問題。
自增的代碼可以用過idea的工具查看到。
GETSTATIC thread/TestThreadFunction.count : I ICONST_1 IADD PUTSTATIC thread/TestThreadFunction.count : I
可以看到,它被拆分成了四條執行去執行。
這個代碼的執行結果就是,最后的結果是小于20000的。
那么,我們現在設計一下,我希望通過上面提到的方法,讓兩個線程交替的執行,這樣不就可以穩定的自增到20000了嗎?
具體怎么做呢,看下面的代碼:
public static int count = 0; public static final Object object = new Object(); public static void main(String[] arg) { Thread t1 = new Thread(new Runnable() { @Override public void run() { try { for (int i = 0; i < 10000; i++) { synchronized (object) { object.notify(); object.wait(); } count++; System.out.println("t1 " + count); } synchronized (object) { object.notify(); } } catch (Throwable e) { } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { try { for (int i = 0; i < 10000; i++) { synchronized (object) { object.notify(); object.wait(); } count++; System.out.println("t2 " + count); } synchronized (object) { object.notify(); } } catch (Throwable e) { } } }); t1.start(); t2.start(); System.out.println("count: " + count); }
首先第一個線程(t1)進入了同步鎖object,調用notify方法,通知別的線程起來干活,但此時沒有任何作用,接下來調用wait,讓自己進入waiting狀態。
接著第二個線程(t2)自然而然就要干起活來,它先調用了notify方法,觸發了一次喚醒,然后調用wait方法也進入了waiting狀態。
t1收到了notify的喚醒,退出臨界區,開始給count自增,本次循環結束,重新notify,wait后進入waiting狀態。
t2被這個notify所喚醒,開始給count自增,本次循環結束,接著重復一樣的過程。
……
就這樣,兩個線程交替的執行了起來。
最終我們得到的結果是這樣的:
count: 0 t1 1 t2 2 t1 3 t2 4 t1 5 t2 6 t1 7 t2 8 t1 9 t2 10 t1 11 ... // 此處省略 t2 19998 t1 19999 t2 20000
我們發現一個問題,就是主線程的最后面的輸出,先執行了,輸出了0,這是怎么回事呢?
這是由于兩個工作線程還沒開始工作,主線程就執行完畢了。那么,我們希望在兩個線程執行完畢后,主線程再輸出一下結果,這個事情可以做到嗎?
我希望一個線程工作完畢了,我再繼續執行
這個不就是join的作用嗎?
于是我們的代碼可以在start兩個線程后,加上join,再輸出。
...// 這部分相同,省略 t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("count: " + count);
這樣的執行結果就是主線程的輸出在最后了。
... // 省略 t1 19997 t2 19998 t1 19999 t2 20000 count: 20000
接下來我們探討一下Thread.yield的實際作用
先將代碼改寫為下面簡單的,通過synchronized關鍵字進行同步的寫法
Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 1000; i++) { synchronized (object) { count++; System.out.println("t111111 " + count); } } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 1000; i++) { synchronized (object) { count++; System.out.println("t2 " + count); } } } });
我們可以通過代碼的輸出,觀察到,線程的調度是非常的緊密的,就是說,總是一段時間t1一直在執行,然后t2再緊密的執行一段時間。
... // 省略 t111111 153 t111111 154 t111111 155 t111111 156 t111111 157 t111111 158 t111111 159 t111111 160 t111111 161 t111111 162 t111111 163 t111111 164 t111111 165 t111111 166 t2 167 t2 168 t2 169 t2 170 t2 171 t2 172 t2 173 t2 174 t2 175 t2 176 t2 177 t2 178 t2 179 t2 180 t2 181 t2 182 ... // 省略
t1連續執行了166次,才輪到t2來執行。一旦t2開始執行,就會一直搶占cpu一段時間。
我們現在加上Thread.yield方法試試
for (int i = 0; i < 1000; i++) { synchronized (object) { count++; System.out.println("t2 " + count); } Thread.yield(); // 加在這里 }
大致的可以看到,線程對cpu的搶占,變得更加謙讓了
t111111 1 t2 2 t2 3 t2 4 t111111 5 t2 6 t2 7 t2 8 t111111 9 t111111 10 t2 11 t2 12 t111111 13 t111111 14 t111111 15 t2 16 t2 17 t2 18 t111111 19 t111111 20 t2 21 t111111 22 t2 23 t2 24 t111111 25 t2 26 t111111 27 t2 28 t111111 29 t111111 30 t2 31 t2 32 ... // 省略總結
Object.wait讓線程進入wait狀態,必須要其他線程喚醒,或者是傳入了時間長度的wait方法,將至多等待傳入的時間長度后自動喚醒。
Object.notify通知任一一個進入等待狀態的線程,notifyAll通知所有
Thread.join讓調用線程阻塞在這個方法上,直到join的線程完全執行完畢,調用線程才會繼續執行。
Thread.yield通知調度器,主動讓出對cpu的占用。
如果你喜歡這篇文章,歡迎點贊評論打賞
更多干貨內容,歡迎關注我的公眾號:好奇碼農君
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/76009.html
摘要:告訴當前執行的線程為線程池中其他具有相同優先級的線程提供機會。不能保證會立即使當前正在執行的線程處于可運行狀態。當達到超時時間時,主線程和是同樣可能的執行者候選。下一篇并發編程線程安全性深層原因 Thread 使用Java的同學對Thread應該不陌生了,線程的創建和啟動等這里就不講了,這篇主要講幾個容易被忽視的方法以及線程狀態遷移。 wait/notify/notifyAll 首先我...
摘要:線程線程是進程中的一個實體,作為系統調度和分派的基本單位。下的線程看作輕量級進程。因此,使用的目的是讓相同優先級的線程之間能適當的輪轉執行。需要注意的是,是線程自己從內部拋出的,并不是方法拋出的。 本文及后續相關文章梳理一下關于多線程和同步鎖的知識,平時只是應用層面的了解,由于最近面試總是問一些原理性的知識,雖說比較反感這種理論派,但是為了生計也必須掌握一番。(PS:并不是說掌握原理不...
摘要:但是,實際中無法保證達到讓步目的,因為讓步的線程還有可能被線程調度程序再次選中。在大多數情況下,將導致線程從運行狀態轉到可運行狀態,但有可能沒有效果。 多線程編程 線程狀態圖 總是無法上傳,稍后上傳 常用函數 狀態轉換 運行中->阻塞 sleep(long millis) 在指定的毫秒數內讓當前正在執行的線程休眠 join() 等待t線程終止 使用方式 Thread t =...
摘要:本文對多線程基礎知識進行梳理,主要包括多線程的基本使用,對象及變量的并發訪問,線程間通信,的使用,定時器,單例模式,以及線程狀態與線程組。源碼采用構建,多線程這部分源碼位于模塊中。通知可能等待該對象的對象鎖的其他線程。 本文對多線程基礎知識進行梳理,主要包括多線程的基本使用,對象及變量的并發訪問,線程間通信,lock的使用,定時器,單例模式,以及線程狀態與線程組。 寫在前面 花了一周時...
閱讀 1591·2021-11-16 11:44
閱讀 7468·2021-09-22 15:00
閱讀 4484·2021-09-02 10:20
閱讀 1949·2021-08-27 16:20
閱讀 2391·2019-08-26 14:00
閱讀 2910·2019-08-26 11:44
閱讀 1641·2019-08-23 18:33
閱讀 1859·2019-08-22 17:28