<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java 線程通信之 wait/notify 機(jī)制

          共 7215字,需瀏覽 15分鐘

           ·

          2021-04-13 21:44

          ????關(guān)注后回復(fù) “進(jìn)群” ,拉你進(jìn)程序員交流群????

          作者丨ytao

          來源丨ytao

          前言

          Java 線程通信是將多個(gè)獨(dú)立的線程個(gè)體進(jìn)行關(guān)聯(lián)處理,使得線程與線程之間能進(jìn)行相互通信。比如線程 A 修改了對(duì)象的值,然后通知給線程 B,使線程 B 能夠知道線程 A 修改的值,這就是線程通信。

          wait/notify 機(jī)制

          一個(gè)線程調(diào)用 Object 的 wait() 方法,使其線程被阻塞;另一線程調(diào)用 Object 的 notify()/notifyAll() 方法,wait() 阻塞的線程繼續(xù)執(zhí)行。

          wai/notify 方法

          方法說明
          wait()當(dāng)前線程被阻塞,線程進(jìn)入 WAITING 狀態(tài)
          wait(long)設(shè)置線程阻塞時(shí)長,線程會(huì)進(jìn)入 TIMED_WAITING 狀態(tài)。如果設(shè)置時(shí)間內(nèi)(毫秒)沒有通知,則超時(shí)返回
          wait(long, int)納秒級(jí)別的線程阻塞時(shí)長設(shè)置
          notify()通知同一個(gè)對(duì)象上已執(zhí)行 wait() 方法且獲得對(duì)象鎖的等待線程
          notifyAll()通知同一對(duì)象上所有等待的線程

          實(shí)現(xiàn) wait/notify 機(jī)制的條件:

          • 調(diào)用 wait 線程和 notify 線程必須擁有相同對(duì)象鎖。

          • wait() 方法和 notify()/notifyAll() 方法必須在 Synchronized 方法或代碼塊中。

          由于 wait/notify 方法是定義在 java.lang.Object中,所以在任何 Java 對(duì)象上都可以使用。

          wait 方法

          在執(zhí)行 wait() 方法前,當(dāng)前線程必須已獲得對(duì)象鎖。調(diào)用它時(shí)會(huì)阻塞當(dāng)前線程,進(jìn)入等待狀態(tài),在當(dāng)前 wait() 處暫停線程。同時(shí),wait() 方法執(zhí)行后,會(huì)立即釋放獲得的對(duì)象鎖。

          下面通過案例來查看 wait() 釋放鎖。

          首先查看不使用 wait() 方法時(shí)的代碼執(zhí)行情況:

          1. package top.ytao.demo.thread.waitnotify;


          2. /**

          3. * Created by YangTao

          4. */

          5. publicclassWaitTest{


          6. staticObject object = newObject();


          7. publicstaticvoid main(String[] args) {


          8. newThread(() -> {

          9. synchronized(object){

          10. System.out.println("開始線程 A");

          11. try{

          12. Thread.sleep(2000L);

          13. } catch(InterruptedException e) {

          14. e.printStackTrace();

          15. }

          16. System.out.println("結(jié)束線程 A");

          17. }

          18. }, "線程 A").start();



          19. newThread(() -> {

          20. try{

          21. Thread.sleep(500L);

          22. } catch(InterruptedException e) {

          23. e.printStackTrace();

          24. }

          25. synchronized(object){

          26. System.out.println("開始線程 B");


          27. System.out.println("結(jié)束線程 B");

          28. }

          29. }, "線程 B").start();


          30. }


          31. }

          創(chuàng)建 A、B 兩個(gè)線程,。首先在 B 線程創(chuàng)建后 sleep ,保證 B 線程的打印后于 A 線程執(zhí)行。在 A 線程中,獲取到對(duì)象鎖后,sleep 一段時(shí)間,且時(shí)間大于 B 線程的 sleep 時(shí)間。

          執(zhí)行結(jié)果為:

          從上圖結(jié)果中,可以看到,B 線程一定等 A 線程執(zhí)行完 synchronize 代碼塊釋放對(duì)象鎖后 A 線程再獲取對(duì)象鎖進(jìn)入 synchronize 代碼塊中。在這過程中,Thread.sleep() 方法也不會(huì)釋放鎖。

          當(dāng)前在 A 線程 synchronize 代碼塊中執(zhí)行 wait() 方法后,就會(huì)主動(dòng)釋放對(duì)象鎖,A 線程代碼如下:

          1. newThread(() -> {

          2. synchronized(object){

          3. System.out.println("開始線程 A");

          4. try{

          5. // 調(diào)用 object 對(duì)象的 wait 方法

          6. object.wait();

          7. Thread.sleep(2000L);

          8. } catch(InterruptedException e) {

          9. e.printStackTrace();

          10. }

          11. System.out.println("結(jié)束線程 A");

          12. }

          13. }, "線程 A").start();

          執(zhí)行結(jié)果(這里結(jié)果圖片放錯(cuò),查看原文有正確圖片):

          同時(shí) A 線程一直處于阻塞狀態(tài),不會(huì)打印 結(jié)束線程A。

          wait(long) 方法是設(shè)置超時(shí)時(shí)間,當(dāng)?shù)却龝r(shí)間大于設(shè)置的超時(shí)時(shí)間后,會(huì)繼續(xù)往 wait(long) 方法后的代碼執(zhí)行。

          1. newThread(() -> {

          2. synchronized(object){

          3. System.out.println("開始線程 A");

          4. try{

          5. object.wait(1000);

          6. Thread.sleep(2000L);

          7. } catch(InterruptedException e) {

          8. e.printStackTrace();

          9. }

          10. System.out.println("結(jié)束線程 A");

          11. }

          12. }, "線程 A").start();

          執(zhí)行結(jié)果

          同理,wait(long, int) 方法與 wait(long) 同樣,只是多個(gè)納秒級(jí)別的時(shí)間設(shè)置。

          notify 方法

          同樣,在執(zhí)行 notify() 方法前,當(dāng)前線程也必須已獲得線程鎖。調(diào)用 notify() 方法后,會(huì)通知一個(gè)執(zhí)行了 wait() 方法的阻塞等待線程,使該等待線程重新獲取到對(duì)象鎖,然后繼續(xù)執(zhí)行 wait() 后面的代碼。但是,與 wait() 方法不同,執(zhí)行 notify() 后,不會(huì)立即釋放對(duì)象鎖,而需要執(zhí)行完 synchronize 的代碼塊或方法才會(huì)釋放鎖,所以接收通知的線程也不會(huì)立即獲得鎖,也需要等待執(zhí)行 notify() 方法的線程釋放鎖后再獲取鎖。

          notify()

          下面是 notify() 方法的使用,實(shí)現(xiàn)一個(gè)完整的 wait/notify 的例子,同時(shí)驗(yàn)證發(fā)出通知后,執(zhí)行 notify() 方法的線程是否立即釋放鎖,執(zhí)行 wait() 方法的線程是否立即獲取鎖。

          1. package top.ytao.demo.thread.waitnotify;


          2. /**

          3. * Created by YangTao

          4. */

          5. publicclassWaitNotifyTest{


          6. staticObject object = newObject();


          7. publicstaticvoid main(String[] args) {

          8. System.out.println();


          9. newThread(() -> {

          10. synchronized(object){

          11. System.out.println("開始線程 A");

          12. try{

          13. object.wait();

          14. System.out.println("A 線程重新獲取到鎖,繼續(xù)進(jìn)行");

          15. } catch(InterruptedException e) {

          16. e.printStackTrace();

          17. }

          18. System.out.println("結(jié)束線程 A");

          19. }

          20. }, "線程 A").start();



          21. newThread(() -> {

          22. try{

          23. Thread.sleep(500L);

          24. } catch(InterruptedException e) {

          25. e.printStackTrace();

          26. }

          27. synchronized(object){

          28. System.out.println("開始線程 B");

          29. object.notify();

          30. System.out.println("線程 B 通知完線程 A");

          31. try{

          32. // 試驗(yàn)執(zhí)行完 notify() 方法后,A 線程是否能立即獲取到鎖

          33. Thread.sleep(2000L);

          34. } catch(InterruptedException e) {

          35. e.printStackTrace();

          36. }

          37. System.out.println("結(jié)束線程 B");

          38. }

          39. }, "線程 B").start();


          40. }


          41. }

          以上 A 線程執(zhí)行 wait() 方法,B 線程執(zhí)行 notify() 方法,執(zhí)行結(jié)果為:

          執(zhí)行結(jié)果中可以看到,B 線程執(zhí)行 notify() 方法后,即使 sleep 了,A 線程也沒有獲取到鎖,可知,notify() 方法并沒有釋放鎖。

          notify() 是通知到等待中的線程,但是調(diào)用一次 notify() 方法,只能通知到一個(gè)執(zhí)行 wait() 方法的等待線程。如果有多個(gè)等待狀態(tài)的線程,則需多次調(diào)用 notify() 方法,通知到線程順序則根據(jù)執(zhí)行 wait() 方法的先后順序進(jìn)行通知。

          下面創(chuàng)建有兩個(gè)執(zhí)行 wait() 方法的線程的代碼:

          1. package top.ytao.demo.thread.waitnotify;


          2. /**

          3. * Created by YangTao

          4. */

          5. publicclassMultiWaitNotifyTest{


          6. staticObject object = newObject();


          7. publicstaticvoid main(String[] args) {

          8. System.out.println();


          9. newThread(() -> {

          10. synchronized(object){

          11. System.out.println("開始線程 A");

          12. try{

          13. object.wait();

          14. } catch(InterruptedException e) {

          15. e.printStackTrace();

          16. }

          17. System.out.println("結(jié)束線程 A");

          18. }

          19. }, "線程 A").start();



          20. newThread(() -> {

          21. try{

          22. Thread.sleep(500L);

          23. } catch(InterruptedException e) {

          24. e.printStackTrace();

          25. }

          26. synchronized(object){

          27. System.out.println("開始線程 B");

          28. try{

          29. object.wait();

          30. } catch(InterruptedException e) {

          31. e.printStackTrace();

          32. }

          33. System.out.println("結(jié)束線程 B");

          34. }

          35. }, "線程 B").start();



          36. newThread(() -> {

          37. try{

          38. Thread.sleep(3000L);

          39. } catch(InterruptedException e) {

          40. e.printStackTrace();

          41. }

          42. synchronized(object){

          43. System.out.println("開始通知線程 C");

          44. object.notify();

          45. object.notify();

          46. System.out.println("結(jié)束通知線程 C");

          47. }

          48. }, "線程 C").start();


          49. }


          50. }

          先 A 線程執(zhí)行 wait() 方法,然后 B 線程執(zhí)行 wait() 方法,最后 C 線程調(diào)用兩次 notify() 方法,執(zhí)行結(jié)果:

          notifyAll()

          通知多個(gè)等待狀態(tài)的線程,通過多次調(diào)用 notify() 方法實(shí)現(xiàn)的方案,在實(shí)際應(yīng)用過程中,實(shí)現(xiàn)過程不太友好,如果是想通知所有等待狀態(tài)的線程,可使用 notifyAll() 方法,就能喚醒所有線程。

          實(shí)現(xiàn)方式,只需將上面 C 線程的多次調(diào)用 notify() 方法部分改為調(diào)用一次 notifyAll() 方法即可。

          1. newThread(() -> {

          2. try{

          3. Thread.sleep(3000L);

          4. } catch(InterruptedException e) {

          5. e.printStackTrace();

          6. }

          7. synchronized(object){

          8. System.out.println("開始通知線程 C");

          9. object.notifyAll();

          10. System.out.println("結(jié)束通知線程 C");

          11. }

          12. }, "線程 C").start();

          執(zhí)行結(jié)果:

          根據(jù)不同 JVM 的實(shí)現(xiàn),notifyAll() 的喚醒順序會(huì)有所不同,當(dāng)前測(cè)試環(huán)境中,以倒序順序喚醒線程。

          實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模式

          生產(chǎn)消費(fèi)者模式就是一個(gè)線程生產(chǎn)數(shù)據(jù)進(jìn)行存儲(chǔ),另一線程進(jìn)行數(shù)據(jù)提取消費(fèi)。下面就以兩個(gè)線程來模擬,生產(chǎn)者生成一個(gè) UUID 存放到 List 對(duì)象中,消費(fèi)者讀取 List 對(duì)象中的數(shù)據(jù),讀取完成后進(jìn)行清除。

          實(shí)現(xiàn)代碼如下:

          1. package top.ytao.demo.thread.waitnotify;


          2. import java.util.ArrayList;

          3. import java.util.List;

          4. import java.util.UUID;


          5. /**

          6. * Created by YangTao

          7. */

          8. publicclassWaitNotifyModelTest{


          9. // 存儲(chǔ)生產(chǎn)者產(chǎn)生的數(shù)據(jù)

          10. staticList<String> list = newArrayList<>();


          11. publicstaticvoid main(String[] args) {


          12. newThread(() -> {

          13. while(true){

          14. synchronized(list){

          15. // 判斷 list 中是否有數(shù)據(jù),如果有數(shù)據(jù)的話,就進(jìn)入等待狀態(tài),等數(shù)據(jù)消費(fèi)完

          16. if(list.size() != 0){

          17. try{

          18. list.wait();

          19. } catch(InterruptedException e) {

          20. e.printStackTrace();

          21. }

          22. }


          23. // list 中沒有數(shù)據(jù)時(shí),產(chǎn)生數(shù)據(jù)添加到 list 中

          24. list.add(UUID.randomUUID().toString());

          25. list.notify();

          26. System.out.println(Thread.currentThread().getName() + list);

          27. }

          28. }

          29. }, "生產(chǎn)者線程 A ").start();



          30. newThread(() -> {

          31. while(true){

          32. synchronized(list){

          33. // 如果 list 中沒有數(shù)據(jù),則進(jìn)入等待狀態(tài),等收到有數(shù)據(jù)通知后再繼續(xù)運(yùn)行

          34. if(list.size() == 0){

          35. try{

          36. list.wait();

          37. } catch(InterruptedException e) {

          38. e.printStackTrace();

          39. }

          40. }


          41. // 有數(shù)據(jù)時(shí),讀取數(shù)據(jù)

          42. System.out.println(Thread.currentThread().getName() + list);

          43. list.notify();

          44. // 讀取完畢,將當(dāng)前這條 UUID 數(shù)據(jù)進(jìn)行清除

          45. list.clear();

          46. }

          47. }

          48. }, "消費(fèi)者線程 B ").start();


          49. }


          50. }

          運(yùn)行結(jié)果:

          生產(chǎn)者線程運(yùn)行時(shí),如果已存在未消費(fèi)的數(shù)據(jù),則當(dāng)前線程進(jìn)入等待狀態(tài),收到通知后,表明數(shù)據(jù)已消費(fèi)完,再繼續(xù)向 list 中添加數(shù)據(jù)。

          消費(fèi)者線程運(yùn)行時(shí),如果不存在未消費(fèi)的數(shù)據(jù),則當(dāng)前線程進(jìn)入等待狀態(tài),收到通知后,表明 List 中已有新數(shù)據(jù)被添加,繼續(xù)執(zhí)行代碼消費(fèi)數(shù)據(jù)并清除。

          不管是生產(chǎn)者還是消費(fèi)者,基于對(duì)象鎖,一次只能一個(gè)線程能獲取到,如果生產(chǎn)者獲取到鎖就校驗(yàn)是否需要生成數(shù)據(jù),如果消費(fèi)者獲取到鎖就校驗(yàn)是否有數(shù)據(jù)可消費(fèi)。

          一個(gè)簡(jiǎn)單的生產(chǎn)者消費(fèi)者模式就以完成。

          總結(jié)

          等待/通知機(jī)制是實(shí)現(xiàn) Java 線程間通信的一種方式,將多線程中,各個(gè)獨(dú)立運(yùn)行的線程通過相互通信來更高效的協(xié)作完成工作,更大效率利用 CPU 處理程序。這也是學(xué)習(xí)或研究 Java 線程的必學(xué)知識(shí)點(diǎn)。

          -End-

          最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

          點(diǎn)擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取

          在看點(diǎn)這里好文分享給更多人↓↓

          瀏覽 27
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成人自拍偷拍无码 | 天天做夜夜爽 | 亚洲成人中文网 | 国产无码片 | 手机在线免费看A |