Java學習筆記46(多線程三:線程之間的通訊),java學習筆記
多個線程在處理同一個資源,但是線程的任務卻不相同,通過一定的手段使各個線程能有效地利用資源,
這種手段即:等待喚醒機制,又稱作線程之間的通訊
涉及到的方法:wait(),notify()
樣本:
兩個線程一個輸入,一個輸出
package demo;public class Resource { public String name; public String sex;}
輸入線程:
package demo;public class Input implements Runnable { private Resource r = new Resource(); public void run() { int i = 0; while (true) { if (i % 2 == 0) { r.name = "張三"; r.sex = "男"; } else { r.name = "李四"; r.sex = "女"; } i++; } }}
輸出線程:
package demo;public class Output implements Runnable { private Resource r = new Resource(); public void run(){ while (true) { System.out.println(r.name+"..."+r.sex); } }}
測試類別:
package demo;public class ThreadDemo { public static void main(String[] args) { Input in = new Input(); Output out = new Output(); Thread tin = new Thread(in); Thread tout = new Thread(out); tin.start(); tout.start(); }}
運行後卻發現輸出的都是null...null
因為輸入線程和輸出線程中建立的Resource對象使不同的
解決null問題:
package demo;public class Input implements Runnable { private Resource r; public Input(Resource r){ this.r = r; } public void run() { int i = 0; while (true) { if (i % 2 == 0) { r.name = "張三"; r.sex = "男"; } else { r.name = "李四"; r.sex = "女"; } i++; } }}
package demo;public class Output implements Runnable { private Resource r; public Output(Resource r){ this.r = r; } public void run(){ while (true) { System.out.println(r.name+"..."+r.sex); } }}
package demo;public class ThreadDemo { public static void main(String[] args) { Resource r = new Resource(); Input in = new Input(r); Output out = new Output(r); Thread tin = new Thread(in); Thread tout = new Thread(out); tin.start(); tout.start(); }}
運行後又發現了另一個問題:
輸出中含有:張三...女或者李四...男,性別出錯
發生原因:
賦值完張三和男後,繼續賦值李四和女,這時候還未還得及賦值女,就進入了輸出線程,這時候就會輸出李四...男
於是想到加上同步:
public void run() { int i = 0; while (true) { synchronized (this) { if (i % 2 == 0) { r.name = "張三"; r.sex = "男"; } else { r.name = "李四"; r.sex = "女"; } i++; } } }
public void run() { while (true) { synchronized (this) { System.out.println(r.name + "..." + r.sex); } } }
然而問題並沒有解決:
原因:
這裡的同步失去了作用,用到的不是一個鎖
解決辦法:
使用一個共同的鎖即可
public void run() { int i = 0; while (true) { synchronized (r) { if (i % 2 == 0) { r.name = "張三"; r.sex = "男"; } else { r.name = "李四"; r.sex = "女"; } i++; } } }
public void run() { while (true) { synchronized (r) { System.out.println(r.name + "..." + r.sex); } } }
這時候就是正常的輸出了
但是還是存在一個問題,我們希望的是張三和李四交錯出現,一個張三一個李四,現在依然是隨機出現的,大片的張三或李四
解決辦法:
先讓input線程賦值,然後讓output線程輸出,並且讓輸入線程等待,不允許再賦值李四,等待輸出張三結束後,再允許李四賦值,依次下去
輸入線程也需要同樣的方式,輸出完後要等待
這時候就需要用到等待喚醒機制:
輸入:賦值後,執行方法wait()永遠等待
輸出:列印後,再輸出等待之前,喚醒輸入notify(),自己再wait()永遠等待
輸入:被喚醒後,重新賦值,必須notify()喚醒輸出的線程,自己再wait()等待
依次迴圈下去
代碼實現:
package demo;public class Resource { public String name; public String sex; public boolean flag = false;}
package demo;public class Input implements Runnable { private Resource r; public Input(Resource r) { this.r = r; } public void run() { int i = 0; while (true) { synchronized (r) { if (r.flag) { try { r.wait(); } catch (Exception e) { } } if (i % 2 == 0) { r.name = "張三"; r.sex = "男"; } else { r.name = "李四"; r.sex = "女"; } r.flag = true; r.notify(); } i++; } }}
package demo;public class Output implements Runnable { private Resource r; public Output(Resource r) { this.r = r; } public void run() { while (true) { synchronized (r) { if (!r.flag) { try { r.wait(); } catch (Exception e) { } } System.out.println(r.name + "..." + r.sex); r.flag = false; r.notify(); } } }}
package demo;public class ThreadDemo { public static void main(String[] args) { Resource r = new Resource(); Input in = new Input(r); Output out = new Output(r); Thread tin = new Thread(in); Thread tout = new Thread(out); tin.start(); tout.start(); }}
這時候就是張三李四交錯輸出了
完成