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