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