多線程的問題中的經典問題是生產者和消費者的問題,就是如何讓線程有序的進行執行,獲取CPU執行時間片的過程是隨機的,如何能夠讓線程有序的進行,Java中提供了等待喚醒機制很好的解決了這個問題!
生產者消費者經典的線程中的問題其實是解決線程中的通訊問題,就是不同種類的線程針對同一資源的操作,這里其實有一張圖很好的闡述了這其中的問題:

1 //代碼中的實體類 2 public class Student { 3 String name; 4 int age; 5 boolean flag; // 默認情況是沒有數據,如果是true,說明有數據 6 } 7 8 public class SetThread implements Runnable { 9 10 private Student s; 11 private int x = 0; 12 13 public SetThread(Student s) { 14 this.s = s; 15 } 16 17 @Override 18 public void run() { 19 while (true) { 20 synchronized (s) { 21 //判斷有沒有 22 if(s.flag){ 23 try { 24 s.wait(); //t1等着,釋放鎖 25 } catch (InterruptedException e) { 26 e.printStackTrace(); 27 } 28 } 29 30 if (x % 2 == 0) { 31 s.name = "林青霞"; 32 s.age = 27; 33 } else { 34 s.name = "劉意"; 35 s.age = 30; 36 } 37 x++; //x=1 38 39 //修改標記 40 s.flag = true; 41 //喚醒線程 42 s.notify(); //喚醒t2,喚醒並不表示你立馬可以執行,必須還得搶CPU的執行權。 43 } 44 //t1有,或者t2有 45 } 46 } 47 } 48 49 public class GetThread implements Runnable { 50 private Student s; 51 52 public GetThread(Student s) { 53 this.s = s; 54 } 55 56 @Override 57 public void run() { 58 while (true) { 59 synchronized (s) { 60 if(!s.flag){ 61 try { 62 s.wait(); //t2就等待了。立即釋放鎖。將來醒過來的時候,是從這里醒過來的時候 63 } catch (InterruptedException e) { 64 e.printStackTrace(); 65 } 66 } 67 68 System.out.println(s.name + "---" + s.age); 69 //林青霞---27 70 //劉意---30 71 72 //修改標記 73 s.flag = false; 74 //喚醒線程 75 s.notify(); //喚醒t1 76 } 77 } 78 } 79 } 80 81 /* 82 * 分析: 83 * 資源類:Student 84 * 設置學生數據:SetThread(生產者) 85 * 獲取學生數據:GetThread(消費者) 86 * 測試類:StudentDemo 87 * 88 * 問題1:按照思路寫代碼,發現數據每次都是:null---0 89 * 原因:我們在每個線程中都創建了新的資源,而我們要求的時候設置和獲取線程的資源應該是同一個 90 * 如何實現呢? 91 * 在外界把這個數據創建出來,通過構造方法傳遞給其他的類。 92 * 93 * 問題2:為了數據的效果好一些,我加入了循環和判斷,給出不同的值,這個時候產生了新的問題 94 * A:同一個數據出現多次 95 * B:姓名和年齡不匹配 96 * 原因: 97 * A:同一個數據出現多次 98 * CPU的一點點時間片的執行權,就足夠你執行很多次。 99 * B:姓名和年齡不匹配 100 * 線程運行的隨機性 101 * 線程安全問題: 102 * A:是否是多線程環境 是 103 * B:是否有共享數據 是 104 * C:是否有多條語句操作共享數據 是 105 * 解決方案: 106 * 加鎖。 107 * 注意: 108 * A:不同種類的線程都要加鎖。 109 * B:不同種類的線程加的鎖必須是同一把。 110 * 111 * 問題3:雖然數據安全了,但是呢,一次一大片不好看,我就想依次的一次一個輸出。 112 * 如何實現呢? 113 * 通過Java提供的等待喚醒機制解決。 114 * 115 * 等待喚醒: 116 * Object類中提供了三個方法: 117 * wait():等待 118 * notify():喚醒單個線程 119 * notifyAll():喚醒所有線程 120 * 為什么這些方法不定義在Thread類中呢? 121 * 這些方法的調用必須通過鎖對象調用,而我們剛才使用的鎖對象是任意鎖對象。 122 * 所以,這些方法必須定義在Object類中。 123 */ 124 public class StudentDemo { 125 public static void main(String[] args) { 126 //創建資源 127 Student s = new Student(); 128 129 //設置和獲取的類 130 SetThread st = new SetThread(s); 131 GetThread gt = new GetThread(s); 132 133 //線程類 134 Thread t1 = new Thread(st); 135 Thread t2 = new Thread(gt); 136 137 //啟動線程 138 t1.start(); 139 t2.start(); 140 }
141 }
線程的狀態轉換圖及常見執行情況:
上述代碼的優化方案:
1 //創建對象的時候實現線程安全 2 public class Student { 3 private String name; 4 private int age; 5 private boolean flag; // 默認情況是沒有數據,如果是true,說明有數據 6 7 public synchronized void set(String name, int age) { 8 // 如果有數據,就等待 9 if (this.flag) { 10 try { 11 this.wait(); 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 } 15 } 16 17 // 設置數據 18 this.name = name; 19 this.age = age; 20 21 // 修改標記 22 this.flag = true; 23 this.notify(); 24 } 25 26 public synchronized void get() { 27 // 如果沒有數據,就等待 28 if (!this.flag) { 29 try { 30 this.wait(); 31 } catch (InterruptedException e) { 32 e.printStackTrace(); 33 } 34 } 35 36 // 獲取數據 37 System.out.println(this.name + "---" + this.age); 38 39 // 修改標記 40 this.flag = false; 41 this.notify(); 42 } 43 } 44 45 public class GetThread implements Runnable { 46 private Student s; 47 48 public GetThread(Student s) { 49 this.s = s; 50 } 51 52 @Override 53 public void run() { 54 while (true) { 55 s.get(); 56 } 57 } 58 } 59 60 public class SetThread implements Runnable { 61 62 private Student s; 63 private int x = 0; 64 65 public SetThread(Student s) { 66 this.s = s; 67 } 68 69 @Override 70 public void run() { 71 while (true) { 72 if (x % 2 == 0) { 73 s.set("林青霞", 27); 74 } else { 75 s.set("劉意", 30); 76 } 77 x++; 78 } 79 } 80 } 81 82 public class StudentDemo { 83 public static void main(String[] args) { 84 //創建資源 85 Student s = new Student(); 86 87 //設置和獲取的類 88 SetThread st = new SetThread(s); 89 GetThread gt = new GetThread(s); 90 91 //線程類 92 Thread t1 = new Thread(st); 93 Thread t2 = new Thread(gt); 94 95 //啟動線程 96 t1.start(); 97 t2.start(); 98 } 99 }
