日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

java面试-Java并发编程(六)——线程间的通信

發布時間:2025/3/15 java 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java面试-Java并发编程(六)——线程间的通信 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

多條線程之間有時需要數據交互,下面介紹五種線程間數據交互的方式,他們的使用場景各有不同。

1. volatile、synchronized關鍵字

PS:關于volatile的詳細介紹請移步至:Java并發編程的藝術(三)——volatile

1.1 如何實現通信?

這兩種方式都采用了同步機制實現多條線程間的數據通信。與其說是“通信”,倒不如說是“共享變量”來的恰當。當一個共享變量被volatile修飾 或 被同步塊包裹后,他們的讀寫操作都會直接操作共享內存,從而各個線程都能看到共享變量最新的值,也就是實現了內存的可見性。

1.2 特點

  • 這種方式本質上是“共享數據”,而非“傳遞數據”;只是從結果來看,數據好像是從寫線程傳遞到了讀線程;
  • 這種通信方式無法指定特定的接收線程。當數據被修改后究竟哪條線程最先訪問到,這由操作系統隨機決定。
  • 總的來說,這種方式并不是真正意義上的“通信”,而是“共享”。

1.3 使用場景

這種方式能“傳遞”變量。當需要傳遞一些公用的變量時就可以使用這種方式。如:傳遞boolean flag,用于表示狀態、傳遞一個存儲所有任務的隊列等。

1.4 例子

用這種方式實現線程的開關控制。

// 用于控制線程當前的執行狀態 private volatile boolean running = false;// 開啟一條線程 Thread thread = new Thread(new Runnable(){void run(){// 開關while(!running){Thread.sleep(1000);}// 執行線程任務doSometing();} }).start();// 開始執行 public void start(){running = true; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

2. 等待/通知機制

2.1 如何實現?

等待/通知機制的實現由Java完成,我們只需調用Object類的幾個方法即可。

  • wait():將當前線程的狀態改為“等待態”,加入等待隊列,釋放鎖;直到當前線程發生中斷或調用了notify方法,這條線程才會被從等待隊列轉移到同步隊列,此時可以開始競爭鎖。
  • wait(long):和wait()功能一樣,只不過多了個超時動作。一旦超時,就會繼續執行wait之后的代碼,它不會拋超時異常!
  • notify():將等待隊列中的一條線程轉移到同步隊列中去。
  • notifyAll():將等待隊列中的所有線程都轉移到同步隊列中去。

2.2 注意點

  • 以上方法都必須放在同步塊中;
  • 并且以上方法都只能由所處同步塊的鎖對象調用;
  • 鎖對象A.notify()/notifyAll()只能喚醒由鎖對象A wait的線程;
  • 調用notify/notifyAll函數后僅僅是將線程從等待隊列轉移到阻塞隊列,只有當該線程競爭到鎖后,才能從wait方法中返回,繼續執行接下來的代碼;

2.3 QA

  • 為什么wait必須放在同步塊中調用??
    因為等待/通知機制需要和共享狀態變量配合使用,一般是先檢查狀態,若狀態為true則執行wait,即包含“先檢查后執行”,因此需要把這一過程加鎖,確保其原子執行。?
    舉個例子:
// 共享的狀態變量 boolean flag = false;// 線程1 Thread t1 = new Thread(new Runnable(){public void run(){while(!flag){wait();}} }).start();// 線程2 Thread t2 = new Thread(new Runnable(){public void run(){flag = true;notifyAll();} }).start();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

上述例子thread1未加同步。當thread1執行到while那行后,判斷其狀態為true,此時若發生上下文切換,線程2開始執行,并一口氣執行完了;此時flag已經是true,然而thread1繼續執行,遇到wait后便進入等待態;但此時已經沒有線程能喚醒它了,因此就一直等待下去。

  • 為什么notify需要加鎖?且必須和wait使用同一把鎖??
    首先,加鎖是為了保證共享變量的內存可見性,讓它發生修改后能直接寫入共享內存,好讓wait所處的線程立即看見。?
    其次,和wait使用同一把鎖是為了確保wait、notify之間的互斥,即:同一時刻,只能有其中一條線程執行。

  • 為什么必須使用同步塊的鎖對象調用wait函數??
    首先,由于wait會釋放鎖,因此通過鎖對象調用wait就是告訴wait釋放哪個鎖。?
    其次,告訴線程,你是在哪個鎖對象上等待的,只有當該鎖對象調用notify時你才能被喚醒。

  • 為什么必須使用同步塊的鎖對象調用notify函數??
    告訴notify,只喚醒在該鎖對象上等待的線程。

2.4 代碼實現

等待/通知機制用于實現生產者和消費者模式。

  • 生產者
synchronized(鎖A){flag = true;// 或者:list.add(xx);鎖A.notify(); }
  • 1
  • 2
  • 3
  • 4
  • 消費者
synchronized(鎖A){// 不滿足條件while(!flag){ // 或者:list.isEmpty()鎖A.wait();}// doSometing…… }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2.5 超時等待模式

在之前的生產者-消費者模式中,如果生產者沒有發出通知,那么消費者將永遠等待下去。為了避免這種情況,我們可以給消費者增加超時等待功能。該功能依托于wait(long)方法,只需在wait前的檢查條件中增加超時標識位,實現如下:

public void get(long mills){synchronized( list ){// 不加超時功能if ( mills <= 0 ) {while( list.isEmpty() ){list.wait();}}// 添加超時功能else {boolean isTimeout = false;while(list.isEmpty() && isTimeout){list.wait(mills);isTimeout = true;}// doSometing……}} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

3. 管道流

3.1 作用

管道流用于在兩個線程之間進行字節流或字符流的傳遞。

3.2 特點

  • 管道流的實現依靠PipedOutputStream、PipedInputStream、PipedWriter、PipedReader。分別對應字節流和字符流。
  • 他們與IO流的區別是:IO流是在硬盤、內存、Socket之間流動,而管道流僅在內存中的兩條線程間流動。

3.3 實現

步驟如下:?
1. 在一條線程中分別創建輸入流和輸出流;?
2. 將輸入流和輸出流連接起來;?
3. 將輸入流和輸出流分別傳遞給兩條線程;?
4. 調用read和write方法就可以實現線程間通信。

// 創建輸入流與輸出流對象 PipedWriter out = new PipedWriter(); PipedReader in = new PipedReader();// 連接輸入輸出流 out.connect(in);// 創建寫線程 class WriteThread extends Thread{private PipedWriter out;public WriteThread(PipedWriter out){this.out = out;}public void run(){out.write("hello concurrent world!");} }// 創建讀線程 class ReaderThread extends Thread{private PipedReader in;public ReaderThread(PipedReader in){this.in = in;}public void run(){in.read();} }//
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

4. join

4.1 作用

  • join能將并發執行的多條線程串行執行;
  • join函數屬于Thread類,通過一個thread對象調用。當在線程B中執行threadA.join()時,線程B將會被阻塞(底層調用wait方法),等到threadA線程運行結束后才會返回join方法。
  • 被等待的那條線程可能會執行很長時間,因此join函數會拋出InterruptedException。當調用threadA.interrupt()后,join函數就會拋出該異常。

4.2 實現

public static void main(String[] args){// 開啟一條線程Thread t = new Thread(new Runnable(){public void run(){// doSometing}}).start();// 調用join,等待t線程執行完畢try{t.join();}catch(InterruptedException e){// 中斷處理……}}

總結

以上是生活随笔為你收集整理的java面试-Java并发编程(六)——线程间的通信的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。