線程間的通訊
直接上一個Demo,模擬生產者和消費者,就是生產了一個物品,然後消費一個物品,就這樣你一下,我一下,讓他們有類似有通訊的功能一樣知道彼此生產了而且消費了。這裏需要考慮兩個問題面向這個demo時候,第一是線程的安全問題,第二是通訊問題,你一下,我一下
1 package com.thread; 2 // 定義一個共享的數據 3 class User { 4 public String name; 5 public String age; 6 boolean flag = true; // 稍後會用到 7 } 8 9 // 模擬生產者 10 class Input extendsThread { 11 // 把共享變量傳遞當生產者中 12 public User u; 13 Input(User u) { 14 this.u = u; 15 } 16 int count = 1;// count只是為了邏輯控制 17 18 @Override 19 public void run() { 20 21 while (true) { 22 if (count==0) { 23 u.name = "劍二十三"; 24 u.age = "999";25 } else { 26 u.name = "XXX"; 27 u.age = "123"; 28 } 29 count = (count+1)%2;// 邏輯控制count等於0 和不等於0 30 31 } 32 33 } 34 } 35 36 class Out extends Thread { 37 // 共享變量傳入消費者中 38 public User u; 39 Out(User u) { 40 this.u = u; 41 } 42 43 @Override 44 public void run() { 45 while (true) { 46 47 System.out.println(u.name + "----" + u.age); 48 49 50 } 51 } 52 } 53 54 // 模擬生產者和消費者,線程間的通訊 55 public class ThreadDemo4 { 56 public static void main(String[] args) { 57 // 創建共享變量,並創建兩個線程,把共享變量出入其中。開啟兩個線程。 58 // 一般理想狀態是為了打印 劍二十三+999 與 XXX+123 兩種字符串 並且 每次你輸出一下我輸出一下。 59 // 實際代碼打印的確是 劍二十三+123 或者 XXX+999 並且還不是 一行一個樣,你輸出一下我輸出一下。 60 User u = new User(); 61 Input i = new Input(u); 62 Out o = new Out(u); 63 i.start(); 64 o.start(); 65 // 部分打印結果 66 // 劍二十三----123 67 // 劍二十三----999 68 // XXX----999 69 // XXX----999 70 // XXX----123 71 72 } 73 74 }
產生這種結果的原因:
1.因為線程之間出現了並發情況,在賦值的過程中,可能剛賦值完 ‘劍二十三’ 還有沒賦值 ‘999’ 的信息,就打印了,所以打印的是上次保存共享變量的結果。
2.線程之間沒有進行通訊,通過打印答案可以知道,並沒有你生產一個我消費一個,你才能在生產一個,我在消費一個。
解決:
首先解決線程安全問題,我們首先想到synchronized 那麽我們就用 synchronized 先來實現線程的安全問題,在解決通訊問題。
1 package com.thread; 2 3 // 定義一個共享的數據 4 class User { 5 public String name; 6 public String age; 7 boolean flag = true; // 稍後通訊時候可能會用到 8 } 9 10 // 模擬生產者 11 class Input extends Thread { 12 // 把共享變量傳遞當生產者中 13 public User u; 14 15 Input(User u) { 16 this.u = u; 17 } 18 19 int count = 1;// count只是為了邏輯控制 20 21 @Override 22 public void run() { 23 24 while (true) { 25 synchronized (u) { // 因為要用同一把鎖,選定為對象鎖 u 26 if (count == 0) { 27 u.name = "劍二十三"; 28 u.age = "999"; 29 } else { 30 u.name = "XXX"; 31 u.age = "123"; 32 } 33 count = (count + 1) % 2;// 邏輯控制count等於0 和不等於0 34 35 } 36 37 } 38 39 } 40 } 41 42 class Out extends Thread { 43 // 共享變量傳入消費者中 44 public User u; 45 46 Out(User u) { 47 this.u = u; 48 } 49 50 @Override 51 public void run() { 52 while (true) { 53 synchronized (u) { // 因為要用同一把鎖,選定為對象鎖 u 54 55 System.out.println(u.name + "----" + u.age); 56 57 58 } 59 } 60 } 61 } 62 63 // 模擬生產者和消費者,線程間的通訊 64 public class ThreadDemo4 { 65 public static void main(String[] args) { 66 // 創建共享變量,並創建兩個線程,把共享變量出入其中。開啟兩個線程。 67 // 實現了線程之間的安全問題,但是並沒有實現通訊 68 User u = new User(); 69 Input i = new Input(u); 70 Out o = new Out(u); 71 i.start(); 72 o.start(); 73 // 部分打印結果 74 // 劍二十三----999 75 // XXX----123 76 // 劍二十三----999 77 // XXX----123 78 // XXX----123 79 // 劍二十三----999 80 } 81 82 }
到了這步,已經解決了線程的安全問題。但是通訊問題沒有得到解決。解決通訊問題有幾個方法 wait(), notify() ,notifyAll()
wait()、notify、notifyAll()方法
wait()、notify()、notifyAll()是三個定義在Object類裏的方法,可以用來控制線程的狀態。
這三個方法最終調用的都是jvm級的native方法。隨著jvm運行平臺的不同可能有些許差異。
1.如果對象調用了wait方法就會使持有該對象的線程把該對象的控制權交出去,然後處於等待狀態。
2.如果對象調用了notify方法就會通知某個正在等待這個對象的控制權的線程可以繼續運行。
3.如果對象調用了notifyAll方法就會通知所有等待這個對象控制權的線程繼續運行。
註意:一定要在線程同步中使用,並且是同一個鎖的資源
1 package com.thread; 2 3 // 定義一個共享的數據 4 class User { 5 public String name; 6 public String age; 7 boolean flag = true; 8 } 9 10 // 模擬生產者 11 class Input extends Thread { 12 // 把共享變量傳遞當生產者中 13 public User u; 14 15 Input(User u) { 16 this.u = u; 17 } 18 19 int count = 1;// count只是為了邏輯控制 20 21 @Override 22 public void run() { 23 24 while (true) { 25 synchronized (u) { // 因為要用同一把鎖,選定為對象鎖 u 26 27 if (!u.flag) { 28 try { 29 u.wait(); 30 } catch (InterruptedException e) { 31 e.printStackTrace(); 32 } 33 } 34 35 if (count == 0) { 36 u.name = "劍二十三"; 37 u.age = "999"; 38 } else { 39 u.name = "XXX"; 40 u.age = "123"; 41 } 42 count = (count + 1) % 2;// 邏輯控制count等於0 和不等於0 43 u.flag = false; // 把共享flag變為false 44 u.notify(); 45 } 46 47 } 48 49 } 50 } 51 52 class Out extends Thread { 53 // 共享變量傳入消費者中 54 public User u; 55 56 Out(User u) { 57 this.u = u; 58 } 59 60 @Override 61 public void run() { 62 while (true) { 63 synchronized (u) { // 因為要用同一把鎖,選定為對象鎖 u 64 if (u.flag) { 65 try { 66 u.wait(); 67 } catch (InterruptedException e) { 68 e.printStackTrace(); 69 } 70 71 System.out.println(u.name + "----" + u.age); 72 } 73 u.flag = true; // 把共享變量flag變為true 74 u.notify(); 75 } 76 } 77 } 78 } 79 80 // 模擬生產者和消費者,線程間的通訊 81 public class ThreadDemo4 { 82 public static void main(String[] args) { 83 // 創建共享變量,並創建兩個線程,把共享變量出入其中。開啟兩個線程。 84 // 實現了通訊 85 User u = new User(); 86 Input i = new Input(u); 87 Out o = new Out(u); 88 i.start(); 89 o.start(); 90 // 部分打印結果 91 // 劍二十三----999 92 // XXX----123 93 // 劍二十三----999 94 // XXX----123 95 // 劍二十三----999 96 // XXX----123 97 // 劍二十三----999 98 // XXX----123 99 } 100 101 }
wait() 把持有該對象的線程控制權交出去!一定要充分理解這句話,只有交出去,第二個線程傳過來的 notify() 才能夠解鎖它,所以他們兩個互相解鎖喚醒,就實現了通訊的功能。
並且不能單獨使用必須在線程同步中使用!!!否則就會報錯
wait與sleep區別?
對於sleep()方法,我們首先要知道該方法是屬於Thread類中的。而wait()方法,則是屬於Object類中的。
sleep()方法導致了程序暫停執行指定的時間,讓出cpu該其他線程,但是他的監控狀態依然保持者,當指定的時間到了又會自動恢復運行狀態。
在調用sleep()方法的過程中,線程不會釋放對象鎖。
而當調用wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法後本線程才進入對象鎖定池準備
獲取對象鎖進入運行狀態。
第二種同步鎖的功能 Lock鎖
Lock 接口與 synchronized 關鍵字的區別
Lock 接口可以嘗試非阻塞地獲取鎖 當前線程嘗試獲取鎖。如果這一時刻鎖沒有被其他線程獲取到,則成功獲取並持有鎖。
Lock 接口能被中斷地獲取鎖 與 synchronized 不同,獲取到鎖的線程能夠響應中斷,當獲取到的鎖的線程被中斷時,中斷異常將會被拋出,同時鎖會被釋放。
Lock 接口在指定的截止時間之前獲取鎖,如果截止時間到了依舊無法獲取鎖,則返回。
Lock寫法
Lock lock = new ReentrantLock(); lock.lock(); try{ //可能會出現線程安全的操作 }finally{ //一定在finally中釋放鎖 //也不能把獲取鎖在try中進行,因為有可能在獲取鎖的時候拋出異常 lock.unlock(); }
|
package com.lock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class User { public String name; public String age; boolean flag = true; // 創建鎖 Lock lock = new ReentrantLock(); } // 模擬生產者 class Input extends Thread { // 把共享變量傳遞當生產者中 public User u; Input(User u) { this.u = u; } int count = 1;// count只是為了邏輯控制 @Override public void run() { while (true) { u.lock.lock(); // 上鎖,邏輯代碼用try起來,可能會拋出異常,所以漢子型finally解鎖 if (!u.flag) { try { if (count == 0) { u.name = "劍二十三"; u.age = "999"; } else { u.name = "XXX"; u.age = "123"; } count = (count + 1) % 2;// 邏輯控制count等於0 和不等於0 u.flag = false; // 把共享flag變為false // u.notify(); } catch (Exception e) { e.printStackTrace(); } finally { u.lock.unlock(); } } } } } class Out extends Thread { // 共享變量傳入消費者中 public User u; Out(User u) { this.u = u; } @Override public void run() { while (true) { u.lock.lock();// 上鎖,邏輯代碼用try起來,可能會拋出異常,所以漢子型finally解鎖 try { if (u.flag) { System.out.println(u.name + "----" + u.age); u.flag = true; // 把共享變量flag變為true } } catch (Exception e) { } finally { u.lock.unlock(); } } } } // 模擬生產者和消費者,線程間的通訊 public class ThreadDemo4 { public static void main(String[] args) { User u = new User(); Input i = new Input(u); Out o = new Out(u); i.start(); o.start(); } }
但是這次的打印結果又不一樣了,雖然實現了高效率的鎖,但是通訊沒了,打印結果是程序在運行,沒用打印記錄。這時候需要使用 Condition
用Condition 代替 nodity 和 wait
Condition用法
Condition的功能類似於在傳統的線程技術中的,Object.wait()和Object.notify()的功能。
代碼
Condition condition = lock.newCondition(); res. condition.await(); 類似wait res. Condition. Signal() 類似notify |
1 package com.lock; 2 3 import java.util.concurrent.locks.Condition; 4 import java.util.concurrent.locks.Lock; 5 import java.util.concurrent.locks.ReentrantLock; 6 7 class User { 8 public String name; 9 public String age; 10 boolean flag = true; 11 // 創建鎖 12 Lock lock = new ReentrantLock(); 13 } 14 15 // 模擬生產者 16 class Input extends Thread { 17 // 把共享變量傳遞當生產者中 18 public User u; 19 Condition condition; 20 21 Input(User u, Condition condition) { 22 this.condition = condition; 23 this.u = u; 24 } 25 26 int count = 1;// count只是為了邏輯控制 27 28 @Override 29 public void run() { 30 31 while (true) { 32 try { 33 u.lock.lock(); // 上鎖,邏輯代碼用try起來,可能會拋出異常,所以漢子型finally解鎖 34 if (!u.flag) { 35 condition.await(); 36 } 37 if (count == 0) { 38 u.name = "劍二十三"; 39 u.age = "999"; 40 } else { 41 u.name = "XXX"; 42 u.age = "123"; 43 } 44 count = (count + 1) % 2;// 邏輯控制count等於0 和不等於0 45 u.flag = false; // 把共享flag變為false 46 condition.signal(); 47 } catch (Exception e) { 48 } finally { 49 u.lock.unlock(); 50 } 51 52 } 53 54 } 55 } 56 57 class Out extends Thread { 58 // 共享變量傳入消費者中 59 public User u; 60 Condition condition; 61 62 Out(User u, Condition condition) { 63 this.condition = condition; 64 this.u = u; 65 } 66 67 @Override 68 public void run() { 69 while (true) { 70 try { 71 u.lock.lock();// 上鎖,邏輯代碼用try起來,可能會拋出異常,所以漢子型finally解鎖 72 73 if (u.flag) { 74 try { 75 condition.await(); 76 } catch (InterruptedException e1) { 77 e1.printStackTrace(); 78 } 79 } 80 u.flag = true; // 把共享變量flag變為true 81 System.out.println(u.name + "----" + u.age); 82 condition.signal(); 83 84 } catch (Exception e) { 85 86 } finally { 87 u.lock.unlock(); 88 } 89 90 } 91 } 92 } 93 94 // 模擬生產者和消費者,線程間的通訊 95 public class ThreadDemo4 { 96 public static void main(String[] args) { 97 User u = new User(); 98 Condition condition = u.lock.newCondition(); 99 Input i = new Input(u, condition); 100 Out o = new Out(u, condition); 101 i.start(); 102 o.start(); 103 } 104 105 }
以上這幾種就是配合使用的線程通訊問題的解決方式。
線程間的通訊