java線程間通信的通俗解釋及代碼示例
線程間通信:由于多線程共享地址空間和數(shù)據(jù)空間,所以多個(gè)線程間的通信是一個(gè)線程的數(shù)據(jù)可以直接提供給其他線程使用,而不必通過(guò)操作系統(tǒng)(也就是內(nèi)核的調(diào)度)。
進(jìn)程間的通信則不同,它的數(shù)據(jù)空間的獨(dú)立性決定了它的通信相對(duì)比較復(fù)雜,需要通過(guò)操作系統(tǒng)。以前進(jìn)程間的通信只能是單機(jī)版的,現(xiàn)在操作系統(tǒng)都繼承了基于套接字(socket)的進(jìn)程間的通信機(jī)制。這樣進(jìn)程間的通信就不局限于單臺(tái)計(jì)算機(jī)了,實(shí)現(xiàn)了網(wǎng)絡(luò)通信。線程通信主要分為以下幾個(gè)部分,下面通過(guò)生活中圖書(shū)館借書(shū)的例子簡(jiǎn)單講解以下:
通過(guò)共享對(duì)象通信
加入圖書(shū)館只有一本《java并發(fā)編程實(shí)戰(zhàn)》,小A早上的時(shí)候把這本書(shū)給借走了,然后下午小B去圖書(shū)館去找這本書(shū),這時(shí)候小A和小B是兩個(gè)線程,《java并發(fā)編程實(shí)戰(zhàn)》就是共享對(duì)象(類似于多線程中的全局變量的資源),小B發(fā)現(xiàn)這本書(shū)已經(jīng)被借走了,所以就回去等了幾天,幾天后,小B又去圖書(shū)館發(fā)現(xiàn)這本書(shū)被還回來(lái)了,就把書(shū)借走了,這就是通過(guò)共享對(duì)象進(jìn)行通信。
忙等待
由于快要BAT實(shí)習(xí)生招聘了,所以小B非常想看這本書(shū),所以小B就每隔一個(gè)小時(shí)(while循環(huán))就去看看這本書(shū)有沒(méi)有被還回來(lái)了,這樣雖然比較耗費(fèi)處理器資源,但是只要書(shū)一旦被還回來(lái),小B就可以馬上知道。
wait() notify() notifyAll()
由于圖書(shū)館隔著宿舍比較近,所以小B發(fā)現(xiàn)每隔一個(gè)小時(shí)就去圖書(shū)館身體有點(diǎn)吃不消,不過(guò)很快,學(xué)校的圖書(shū)館系統(tǒng)增加了短信提醒功能(notify()),所以小B可以一邊睡覺(jué)一邊等短信。
丟失的信號(hào)
圖書(shū)館系統(tǒng)是這么設(shè)計(jì)的,當(dāng)有一本書(shū)被還回來(lái)的時(shí)候,就會(huì)給等待者發(fā)短信,但是短信只能發(fā)送一次,如果沒(méi)有等待者,短信也會(huì)發(fā)出(只不過(guò)這個(gè)時(shí)候沒(méi)有沒(méi)有接受者),問(wèn)題出現(xiàn)了,因?yàn)槎绦胖粫?huì)發(fā)一次,當(dāng)書(shū)被還回來(lái)的時(shí)候,沒(méi)有人等待借書(shū),他會(huì)發(fā)一條空短信,但是之后有等待借此本書(shū)的同學(xué)永遠(yuǎn)也不會(huì)再收到短信,導(dǎo)致這些同學(xué)會(huì)無(wú)休止的等待。為了解決這個(gè)問(wèn)題,我們要進(jìn)入等待狀態(tài)的時(shí)候先打電話問(wèn)問(wèn)圖書(shū)館阿姨是否需要繼續(xù)等待。
假喚醒
圖書(shū)館系統(tǒng)有一個(gè)bug,會(huì)是不是給用戶發(fā)送錯(cuò)誤短信,我們很聽(tīng)話,收到短信就會(huì)去圖書(shū)館借書(shū),但是到達(dá)圖書(shū)館后發(fā)現(xiàn)書(shū)根本就沒(méi)有被還回來(lái),然后接著做其他的事情。
線程間的通信方式
#鎖機(jī)制:包括互斥鎖、條件變量、讀寫(xiě)鎖
*互斥鎖提供了以排他方式防止數(shù)據(jù)結(jié)構(gòu)被并發(fā)修改的方法。
*讀寫(xiě)鎖允許多個(gè)線程同時(shí)讀共享數(shù)據(jù),而對(duì)寫(xiě)操作是互斥的。
*條件變量可以以原子的方式阻塞進(jìn)程,直到某個(gè)特定條件為真為止。對(duì)條件的測(cè)試是在互斥鎖的保護(hù)下進(jìn)行的。條件變量始終與互斥鎖一起使用。
#信號(hào)量機(jī)制(Semaphore):包括無(wú)名線程信號(hào)量和命名線程信號(hào)量
#信號(hào)機(jī)制(Signal):類似進(jìn)程間的信號(hào)處理
線程間的通信目的主要是用于線程同步,所以線程沒(méi)有像進(jìn)程通信中的用于數(shù)據(jù)交換的通信機(jī)制。
我覺(jué)得我對(duì)線程通信的理解還是不夠徹底,下面分享幾段代碼,幫助理解:
①同步
這里講的同步是指多個(gè)線程通過(guò)synchronized關(guān)鍵字這種方式來(lái)實(shí)現(xiàn)線程間的通信。
參考示例:
public class MyObject { synchronized public void methodA() { //do something.... } synchronized public void methodB() { //do some other thing } } public class ThreadA extends Thread { private MyObject object; //省略構(gòu)造方法 @Override public void run() { super.run(); object.methodA(); } } public class ThreadB extends Thread { private MyObject object; //省略構(gòu)造方法 @Override public void run() { super.run(); object.methodB(); } } public class Run { public static void main(String[] args) { MyObject object = new MyObject(); //線程A與線程B 持有的是同一個(gè)對(duì)象:object ThreadA a = new ThreadA(object); ThreadB b = new ThreadB(object); a.start(); b.start(); } }
由于線程A和線程B持有同一個(gè)MyObject類的對(duì)象object,盡管這兩個(gè)線程需要調(diào)用不同的方法,但是它們是同步執(zhí)行的,比如:線程B需要等待線程A執(zhí)行完了methodA()方法之后,它才能執(zhí)行methodB()方法。這樣,線程A和線程B就實(shí)現(xiàn)了通信。
這種方式,本質(zhì)上就是“共享內(nèi)存”式的通信。多個(gè)線程需要訪問(wèn)同一個(gè)共享變量,誰(shuí)拿到了鎖(獲得了訪問(wèn)權(quán)限),誰(shuí)就可以執(zhí)行。
②while輪詢的方式
代碼如下:
import java.util.ArrayList; import java.util.List; public class MyList { private List<String> list = new ArrayList<String>(); public void add() { list.add("elements"); } public int size() { return list.size(); } } import mylist.MyList; public class ThreadA extends Thread { private MyList list; public ThreadA(MyList list) { super(); this.list = list; } @Override public void run() { try { for (int i = 0; i < 10; i++) { list.add(); System.out.println("添加了" + (i + 1) + "個(gè)元素"); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } } } import mylist.MyList; public class ThreadB extends Thread { private MyList list; public ThreadB(MyList list) { super(); this.list = list; } @Override public void run() { try { while (true) { if (list.size() == 5) { System.out.println("==5, 線程b準(zhǔn)備退出了"); throw new InterruptedException(); } } } catch (InterruptedException e) { e.printStackTrace(); } } } import mylist.MyList; import extthread.ThreadA; import extthread.ThreadB; public class Test { public static void main(String[] args) { MyList service = new MyList(); ThreadA a = new ThreadA(service); a.setName("A"); a.start(); ThreadB b = new ThreadB(service); b.setName("B"); b.start(); } }
在這種方式下,線程A不斷地改變條件,線程ThreadB不停地通過(guò)while語(yǔ)句檢測(cè)這個(gè)條件(list.size()==5)是否成立 ,從而實(shí)現(xiàn)了線程間的通信。但是這種方式會(huì)浪費(fèi)CPU資源。之所以說(shuō)它浪費(fèi)資源,是因?yàn)镴VM調(diào)度器將CPU交給線程B執(zhí)行時(shí),它沒(méi)做啥“有用”的工作,只是在不斷地測(cè)試 某個(gè)條件是否成立。就類似于現(xiàn)實(shí)生活中,某個(gè)人一直看著手機(jī)屏幕是否有電話來(lái)了,而不是: 在干別的事情,當(dāng)有電話來(lái)時(shí),響鈴?fù)ㄖ猅A電話來(lái)了。關(guān)于線程的輪詢的影響
③wait/notify機(jī)制
代碼如下:
import java.util.ArrayList; import java.util.List; public class MyList { private static List<String> list = new ArrayList<String>(); public static void add() { list.add("anyString"); } public static int size() { return list.size(); } } public class ThreadA extends Thread { private Object lock; public ThreadA(Object lock) { super(); this.lock = lock; } @Override public void run() { try { synchronized (lock) { if (MyList.size() != 5) { System.out.println("wait begin " + System.currentTimeMillis()); lock.wait(); System.out.println("wait end " + System.currentTimeMillis()); } } } catch (InterruptedException e) { e.printStackTrace(); } } } public class ThreadB extends Thread { private Object lock; public ThreadB(Object lock) { super(); this.lock = lock; } @Override public void run() { try { synchronized (lock) { for (int i = 0; i < 10; i++) { MyList.add(); if (MyList.size() == 5) { lock.notify(); System.out.println("已經(jīng)發(fā)出了通知"); } System.out.println("添加了" + (i + 1) + "個(gè)元素!"); Thread.sleep(1000); } } } catch (InterruptedException e) { e.printStackTrace(); } } } public class Run { public static void main(String[] args) { try { Object lock = new Object(); ThreadA a = new ThreadA(lock); a.start(); Thread.sleep(50); ThreadB b = new ThreadB(lock); b.start(); } catch (InterruptedException e) { e.printStackTrace(); } } }
線程A要等待某個(gè)條件滿足時(shí)(list.size()==5),才執(zhí)行操作。線程B則向list中添加元素,改變list的size。
A,B之間如何通信的呢?也就是說(shuō),線程A如何知道list.size()已經(jīng)為5了呢?
這里用到了Object類的wait()和notify()方法。
當(dāng)條件未滿足時(shí)(list.size()!=5),線程A調(diào)用wait()放棄CPU,并進(jìn)入阻塞狀態(tài)。---不像②while輪詢那樣占用CPU
當(dāng)條件滿足時(shí),線程B調(diào)用notify()通知線程A,所謂通知線程A,就是喚醒線程A,并讓它進(jìn)入可運(yùn)行狀態(tài)。
這種方式的一個(gè)好處就是CPU的利用率提高了。
但是也有一些缺點(diǎn):比如,線程B先執(zhí)行,一下子添加了5個(gè)元素并調(diào)用了notify()發(fā)送了通知,而此時(shí)線程A還執(zhí)行;當(dāng)線程A執(zhí)行并調(diào)用wait()時(shí),那它永遠(yuǎn)就不可能被喚醒了。因?yàn)?,線程B已經(jīng)發(fā)了通知了,以后不再發(fā)通知了。這說(shuō)明:通知過(guò)早,會(huì)打亂程序的執(zhí)行邏輯。
④管道通信就是使用java.io.PipedInputStream和java.io.PipedOutputStream進(jìn)行通信
具體就不介紹了。分布式系統(tǒng)中說(shuō)的兩種通信機(jī)制:共享內(nèi)存機(jī)制和消息通信機(jī)制。感覺(jué)前面的①中的synchronized關(guān)鍵字和②中的while輪詢“屬于”共享內(nèi)存機(jī)制,由于是輪詢的條件使用了volatile關(guān)鍵字修飾時(shí),這就表示它們通過(guò)判斷這個(gè)“共享的條件變量“是否改變了,來(lái)實(shí)現(xiàn)進(jìn)程間的交流。
而管道通信,更像消息傳遞機(jī)制,也就是說(shuō):通過(guò)管道,將一個(gè)線程中的消息發(fā)送給另一個(gè)。
總結(jié)
以上就是本文關(guān)于java線程間通信的通俗解釋的全部?jī)?nèi)容,希望對(duì)大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站:
Java編程中實(shí)現(xiàn)Condition控制線程通信
如有不足之處,歡迎留言指出。
相關(guān)文章
Java實(shí)現(xiàn)過(guò)濾掉map集合中key或value為空的值示例
這篇文章主要介紹了Java實(shí)現(xiàn)過(guò)濾掉map集合中key或value為空的值,涉及java針對(duì)map的簡(jiǎn)單遍歷、判斷、移除等相關(guān)操作技巧,需要的朋友可以參考下2018-06-06Java struts2 validate用戶登錄校驗(yàn)功能實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了Java struts2 validate用戶登錄校驗(yàn)功能實(shí)現(xiàn)的具體步驟,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-05-05springboot上傳文件,url直接訪問(wèn)資源問(wèn)題
這篇文章主要介紹了springboot上傳文件,url直接訪問(wèn)資源問(wèn)題。具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11Java游戲俄羅斯方塊的實(shí)現(xiàn)實(shí)例
這篇文章主要介紹了Java游戲俄羅斯方塊的實(shí)現(xiàn)實(shí)例的相關(guān)資料,這里實(shí)現(xiàn)簡(jiǎn)單的俄羅斯方塊幫助大家學(xué)習(xí)理解基礎(chǔ)知識(shí),需要的朋友可以參考下2017-08-08現(xiàn)代高效的java構(gòu)建工具gradle的快速入門(mén)
和Maven一樣,Gradle只是提供了構(gòu)建項(xiàng)目的一個(gè)框架,真正起作用的是Plugin,本文主要介紹了gradle入門(mén),文中通過(guò)示例代碼介紹的非常詳細(xì),感興趣的小伙伴們可以參考一下2021-11-11java實(shí)現(xiàn)163郵箱發(fā)送郵件到qq郵箱成功案例
這篇文章主要為大家分享了java實(shí)現(xiàn)163郵箱發(fā)送郵件到qq郵箱成功案例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-05-05