摘要:線程間通信其實(shí)就是多個(gè)線程操作同一個(gè)資源,但動(dòng)作不同。同步前提是多線程。將該線程載入線程池,等待喚醒。該方法拋出異常,故需要配合使用隨機(jī)喚醒線程池中一線程。線程為了檢測(cè)死鎖,它需要遞進(jìn)地檢測(cè)所有被請(qǐng)求的鎖。
線程間通信
其實(shí)就是多個(gè)線程操作同一個(gè)資源,但動(dòng)作不同。
示例:在某個(gè)數(shù)據(jù)庫(kù)中,Input輸入人的姓名,性別,Output輸出,兩個(gè)線程同時(shí)作用。
思考:1.明確哪些代碼是多線程操作的?2.明確共享數(shù)據(jù)。3.明確多線程代碼中哪些是共享數(shù)據(jù)的。
思考后發(fā)現(xiàn),Input和Output類(lèi)中的run方法對(duì)Res類(lèi)的Field數(shù)據(jù)同時(shí)操作。故需要考慮使用同步。
同步前提:1.是多線程。2.必須是多個(gè)線程使用同一個(gè)鎖
唯一的鎖有:類(lèi)字節(jié)碼文件(非靜態(tài)同步函數(shù)不推薦),資源對(duì)象r
class Res //共同處理的資源庫(kù),包含兩個(gè)屬性 { String name; String sex; } class Input implements Runnable { private Res r; Input (Res r) { this.r = r; } public void run() { int x = 0; while (true) { synchronized (r) { if (x==0) { r.name="mike"; r.sex="male"; x=1; } else { r.name="莉莉"; r.sex="女女女"; x=0; } } } } } class Output implements Runnable { private Res r; Output (Res r) { this.r = r; } public void run() { while (true) { synchronized (r) { System.out.println(r.name+"————"+r.sex); } } } } class InputoutputDemo { public static void main(String[] args) { Res r = new Res(); Input in = new Input(r); Output out = new Output(r); Thread t1 = new Thread(in); Thread t2 = new Thread(out); t1.start(); t2.start(); } }
觀察結(jié)果:
由于輸入線程一直搶奪資源,導(dǎo)致輸出線程長(zhǎng)時(shí)間屬于阻塞狀態(tài)。為了使其達(dá)到輸入-輸出的行為,考慮等待喚醒機(jī)制。
注意:以下三種方法使用時(shí)要求必須有監(jiān)視器(鎖),因此必須使用在同步里。需要標(biāo)示他們所操作線程持有的鎖。等待和喚醒必須是同一個(gè)鎖。
-wait();將該線程載入線程池,等待喚醒。(該方法拋出異常,故需要配合try catch使用)
-notify();隨機(jī)喚醒線程池中一線程。
-notifyAll();喚醒線程池中所有線程。
代碼如下:
class Res //共同處理的資源庫(kù) { String name; String sex; boolean flag = false; //標(biāo)識(shí)位來(lái)表示和判斷已輸入or已輸出 } class Input implements Runnable { private Res r; Input (Res r) { this.r = r; } public void run() { int x = 0; while (true) { synchronized (r) { if (r.flag) //如果標(biāo)識(shí)位為真,說(shuō)明已經(jīng)輸入,此時(shí)關(guān)閉輸入,等待輸出 { try { r.wait();//wait配合try catch使用,且要標(biāo)識(shí)鎖。 } catch (Exception e) { } } else //否則輸入數(shù)據(jù),置標(biāo)識(shí)位為真并喚醒輸出。 { if (x==0) { r.name="mike"; r.sex="male"; x=1; } else { r.name="莉莉"; r.sex="女女女"; x=0; } r.flag = true; r.notify(); //喚醒輸出 } } } } } class Output implements Runnable { private Res r; Output (Res r) { this.r = r; } public void run() { while (true) { synchronized (r) { if (r.flag) //如果標(biāo)識(shí)位為真,則有數(shù)據(jù)等待輸出,此時(shí)取出數(shù)據(jù)后置標(biāo)識(shí)位為假,喚醒輸入 { System.out.println(r.name+"————"+r.sex); r.flag = false; r.notify(); } else //否則關(guān)閉輸出。等待輸入 try { r.wait(); } catch (Exception e) { } } } } } class InputoutputDemo { public static void main(String[] args) { Res r = new Res(); Input in = new Input(r); Output out = new Output(r); Thread t1 = new Thread(in); Thread t2 = new Thread(out); t1.start(); t2.start(); } }
最后考慮到設(shè)計(jì)慣例,封裝數(shù)據(jù)和操作方法,優(yōu)化后代碼如下(參考設(shè)計(jì)思路和設(shè)計(jì)慣例)
class Res //共同處理的資源庫(kù) { private String name; private String sex; private boolean flag = false; //標(biāo)識(shí)位來(lái)表示和判斷已輸入or已輸出 public synchronized void set(String name,String sex) { if (flag) try { this.wait(); //非靜態(tài)同步函數(shù)的鎖為this } catch (Exception e) { } this.name = name; this.sex = sex; flag = true; this.notify(); } public synchronized void out() { if (!flag) try { this.wait(); } catch (Exception e) { } System.out.println(name+"......."+sex); flag = false; this.notify(); } } class Input implements Runnable { private Res r; Input (Res r) { this.r = r; } public void run() { int x = 0; while (true) { if (x==0) r.set("mike","male"); else r.set("莉莉","女女女女"); x = (x+1)%2; } } } class Output implements Runnable { private Res r; Output (Res r) { this.r = r; } public void run() { while (true) { r.out(); } } } class InputoutputDemo { public static void main(String[] args) { Res r = new Res(); new Thread(new Input(r)).start(); //匿名對(duì)象,簡(jiǎn)化代碼 new Thread(new Output(r)).start(); /* Input in = new Input(r); Output out = new Output(r); Thread t1 = new Thread(in); Thread t2 = new Thread(out); t1.start(); t2.start(); */ } }避免死鎖的方法:
1.加鎖順序
當(dāng)多個(gè)線程需要相同的一些鎖,但是按照不同的順序加鎖,死鎖就很容易發(fā)生。
如果能確保所有的線程都是按照相同的順序獲得鎖,那么死鎖就不會(huì)發(fā)生??聪旅孢@個(gè)例子:
Thread 1: lock A lock B Thread 2: wait for A lock C (when A locked) Thread 3: wait for A wait for B wait for C
如果一個(gè)線程(比如線程3)需要一些鎖,那么它必須按照確定的順序獲取鎖。它只有獲得了從順序上排在前面的鎖之后,才能獲取后面的鎖。按照順序加鎖是一種有效的死鎖預(yù)防機(jī)制。但是,這種方式需要你事先知道所有可能會(huì)用到的鎖,并對(duì)這些鎖做適當(dāng)?shù)呐判颍傆行r(shí)候是無(wú)法預(yù)知的。
2.加鎖時(shí)限
另外一個(gè)可以避免死鎖的方法是在嘗試獲取鎖的時(shí)候加一個(gè)超時(shí)時(shí)間,這也就意味著在嘗試獲取鎖的過(guò)程中若超過(guò)了這個(gè)時(shí)限該線程則放棄對(duì)該鎖請(qǐng)求。若一個(gè)線程沒(méi)有在給定的時(shí)限內(nèi)成功獲得所有需要的鎖,則會(huì)進(jìn)行回退并釋放所有已經(jīng)獲得的鎖,然后等待一段隨機(jī)的時(shí)間再重試。這段隨機(jī)的等待時(shí)間讓其它線程有機(jī)會(huì)嘗試獲取相同的這些鎖,并且讓該應(yīng)用在沒(méi)有獲得鎖的時(shí)候可以繼續(xù)運(yùn)行。此外,如果有非常多的線程同一時(shí)間去競(jìng)爭(zhēng)同一批資源,就算有超時(shí)和回退機(jī)制,還是可能會(huì)導(dǎo)致這些線程重復(fù)地嘗試但卻始終得不到鎖,因?yàn)檫@些線程等待相等的重試時(shí)間的概率就高的多。
這種機(jī)制存在一個(gè)問(wèn)題,在Java中不能對(duì)synchronized同步塊設(shè)置超時(shí)時(shí)間。你需要?jiǎng)?chuàng)建一個(gè)自定義鎖,或使用Java5中java.util.concurrent包下的工具。寫(xiě)一個(gè)自定義鎖類(lèi)不復(fù)雜,但超出了本文的內(nèi)容。
3.死鎖檢測(cè)
死鎖檢測(cè)是一個(gè)更好的死鎖預(yù)防機(jī)制,它主要是針對(duì)那些不可能實(shí)現(xiàn)按序加鎖并且鎖超時(shí)也不可行的場(chǎng)景。
每當(dāng)一個(gè)線程獲得了鎖,會(huì)在線程和鎖相關(guān)的數(shù)據(jù)結(jié)構(gòu)中(map、graph等等)將其記下。除此之外,每當(dāng)有線程請(qǐng)求鎖,也需要記錄在這個(gè)數(shù)據(jù)結(jié)構(gòu)中。
當(dāng)一個(gè)線程請(qǐng)求鎖失敗時(shí),這個(gè)線程可以遍歷鎖的關(guān)系圖看看是否有死鎖發(fā)生。例如,線程A請(qǐng)求鎖7,但是鎖7這個(gè)時(shí)候被線程B持有,這時(shí)線程A就可以檢查一下線程B是否已經(jīng)請(qǐng)求了線程A當(dāng)前所持有的鎖。如果線程B確實(shí)有這樣的請(qǐng)求,那么就是發(fā)生了死鎖(線程A擁有鎖1,請(qǐng)求鎖7;線程B擁有鎖7,請(qǐng)求鎖1)。
當(dāng)然,死鎖一般要比兩個(gè)線程互相持有對(duì)方的鎖這種情況要復(fù)雜的多。線程A等待線程B,線程B等待線程C,線程C等待線程D,線程D又在等待線程A。線程A為了檢測(cè)死鎖,它需要遞進(jìn)地檢測(cè)所有被B請(qǐng)求的鎖。從線程B所請(qǐng)求的鎖開(kāi)始,線程A找到了線程C,然后又找到了線程D,發(fā)現(xiàn)線程D請(qǐng)求的鎖被線程A自己持有著。這是它就知道發(fā)生了死鎖。
下面是一幅關(guān)于四個(gè)線程(A,B,C和D)之間鎖占有和請(qǐng)求的關(guān)系圖。像這樣的數(shù)據(jù)結(jié)構(gòu)就可以被用來(lái)檢測(cè)死鎖。
那么當(dāng)檢測(cè)出死鎖時(shí),這些線程該做些什么呢?
一個(gè)可行的做法是釋放所有鎖,回退,并且等待一段隨機(jī)的時(shí)間后重試。這個(gè)和簡(jiǎn)單的加鎖超時(shí)類(lèi)似,不一樣的是只有死鎖已經(jīng)發(fā)生了才回退,而不會(huì)是因?yàn)榧渔i的請(qǐng)求超時(shí)了。雖然有回退和等待,但是如果有大量的線程競(jìng)爭(zhēng)同一批鎖,它們還是會(huì)重復(fù)地死鎖,原因同超時(shí)類(lèi)似,不能從根本上減輕競(jìng)爭(zhēng)。
一個(gè)更好的方案是給這些線程設(shè)置優(yōu)先級(jí),讓一個(gè)(或幾個(gè))線程回退,剩下的線程就像沒(méi)發(fā)生死鎖一樣繼續(xù)保持著它們需要的鎖。如果賦予這些線程的優(yōu)先級(jí)是固定不變的,同一批線程總是會(huì)擁有更高的優(yōu)先級(jí)。為避免這個(gè)問(wèn)題,可以在死鎖發(fā)生的時(shí)候設(shè)置隨機(jī)的優(yōu)先級(jí)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/69785.html
摘要:本文對(duì)多線程基礎(chǔ)知識(shí)進(jìn)行梳理,主要包括多線程的基本使用,對(duì)象及變量的并發(fā)訪問(wèn),線程間通信,的使用,定時(shí)器,單例模式,以及線程狀態(tài)與線程組。源碼采用構(gòu)建,多線程這部分源碼位于模塊中。通知可能等待該對(duì)象的對(duì)象鎖的其他線程。 本文對(duì)多線程基礎(chǔ)知識(shí)進(jìn)行梳理,主要包括多線程的基本使用,對(duì)象及變量的并發(fā)訪問(wèn),線程間通信,lock的使用,定時(shí)器,單例模式,以及線程狀態(tài)與線程組。 寫(xiě)在前面 花了一周時(shí)...
摘要:并發(fā)模塊本身有兩種不同的類(lèi)型進(jìn)程和線程,兩個(gè)基本的執(zhí)行單元。調(diào)用以啟動(dòng)新線程。在大多數(shù)系統(tǒng)中,時(shí)間片發(fā)生不可預(yù)知的和非確定性的,這意味著線程可能隨時(shí)暫?;蚧謴?fù)。 大綱 什么是并發(fā)編程?進(jìn)程,線程和時(shí)間片交織和競(jìng)爭(zhēng)條件線程安全 策略1:監(jiān)禁 策略2:不可變性 策略3:使用線程安全數(shù)據(jù)類(lèi)型 策略4:鎖定和同步 如何做安全論證總結(jié) 什么是并發(fā)編程? 并發(fā)并發(fā)性:多個(gè)計(jì)算同時(shí)發(fā)生。 在現(xiàn)代...
摘要:線程通信的目標(biāo)是使線程間能夠互相發(fā)送信號(hào)。但是,這個(gè)標(biāo)志已經(jīng)被第一個(gè)喚醒的線程清除了,所以其余醒來(lái)的線程將回到等待狀態(tài),直到下次信號(hào)到來(lái)。如果方法調(diào)用,而非,所有等待線程都會(huì)被喚醒并依次檢查信號(hào)值。 線程通信的目標(biāo)是使線程間能夠互相發(fā)送信號(hào)。另一方面,線程通信使線程能夠等待其他線程的信號(hào)。 showImg(http://segmentfault.com/img/bVbPLD); 例...
摘要:自己實(shí)現(xiàn)在自己實(shí)現(xiàn)之前先搞清楚阻塞隊(duì)列的幾個(gè)特點(diǎn)基本隊(duì)列特性先進(jìn)先出。消費(fèi)隊(duì)列空時(shí)會(huì)阻塞直到寫(xiě)入線程寫(xiě)入了隊(duì)列數(shù)據(jù)后喚醒消費(fèi)線程。最終的隊(duì)列大小為,可見(jiàn)線程也是安全的。 showImg(https://segmentfault.com/img/remote/1460000018811340); 前言 較長(zhǎng)一段時(shí)間以來(lái)我都發(fā)現(xiàn)不少開(kāi)發(fā)者對(duì) jdk 中的 J.U.C(java.util.c...
摘要:線程需要避免竟態(tài),死鎖以及很多其他共享狀態(tài)的并發(fā)性問(wèn)題。用戶(hù)線程在前臺(tái),守護(hù)線程在后臺(tái)運(yùn)行,為其他前臺(tái)線程提供服務(wù)。當(dāng)所有前臺(tái)線程都退出時(shí),守護(hù)線程就會(huì)退出。線程阻塞等待獲取某個(gè)對(duì)象鎖的訪問(wèn)權(quán)限。 1、多線程介紹 多線程優(yōu)點(diǎn) 資源利用率好 程序設(shè)計(jì)簡(jiǎn)單 服務(wù)器響應(yīng)更快 多線程缺點(diǎn) 設(shè)計(jì)更復(fù)雜 上下文切換的開(kāi)銷(xiāo) 增加資源消耗線程需要內(nèi)存維護(hù)本地的堆棧,同時(shí)需要操作系統(tǒng)資源管理線程。...
閱讀 939·2021-11-15 11:38
閱讀 2628·2021-09-08 09:45
閱讀 2912·2021-09-04 16:48
閱讀 2630·2019-08-30 15:54
閱讀 1002·2019-08-30 13:57
閱讀 1685·2019-08-29 15:39
閱讀 569·2019-08-29 12:46
閱讀 3584·2019-08-26 13:39