线程之间通信 等待(wait)和通知(notify)
線程通信概念:
????線程是操作系統(tǒng)中獨立的個體,但這些個體如果不經(jīng)過特殊的處理就不能成為一個整體,線程之間的通信就成為整體的必用方式之一。當線程存在通信指揮,系統(tǒng)間的交互性會更強大,在提高CPU利用率的同時還會對線程任務在處理過程中進行有效的把控與監(jiān)督。
為了支持多線程之間的協(xié)作,JDK提供了兩個非常重要的接口線程等待wait()方法和通知notify()方法。這兩個方法并不是在Thread類中的,而是輸出Object類。這也意味著任何對象都可以調(diào)用這2個方法。
我們先看一個簡單的例子:
1 public class ListAdd1 { 2 private volatile static List list = new ArrayList(); 3 public void add(){ 4 list.add("jianzh5"); 5 } 6 public int size(){ 7 return list.size(); 8 } 9 10 public static void main(String[] args) { 11 final ListAdd1 list1 = new ListAdd1(); 12 Thread t1 = new Thread(new Runnable() { 13 @Override 14 public void run() { 15 try { 16 for(int i = 0; i <10; i++){ 17 list1.add(); 18 System.out.println("當前線程:" + Thread.currentThread().getName() + "添加了一個元素.."); 19 Thread.sleep(500); 20 } 21 } catch (InterruptedException e) { 22 e.printStackTrace(); 23 } 24 } 25 }, "t1"); 26 27 Thread t2 = new Thread(new Runnable() { 28 @Override 29 public void run() { 30 while(true){ 31 if(list1.size() == 5){ 32 System.out.println("當前線程收到通知:" + Thread.currentThread().getName() + " list size = 5 線程停止.."); 33 throw new RuntimeException(); 34 } 35 } 36 } 37 }, "t2"); 38 t1.start(); 39 t2.start(); 40 } 41 }代碼很簡單,這是在沒使用JDK線程協(xié)作時的做法。線程t2一直在死循環(huán),當list的size等于5時退出t2,t1則繼續(xù)運行。
這樣其實也可以是說線程之間的協(xié)作,但是問題就是t2會一直循環(huán)運行,浪費了CPU資源(PS:list必須使用關(guān)鍵字volatile修飾)。
我們再看使用wait和notify時的代碼:
1 public class ListAdd2 { 2 private volatile static List list = new ArrayList(); 3 4 public void add(){ 5 list.add("jianzh5"); 6 } 7 public int size(){ 8 return list.size(); 9 } 10 11 public static void main(String[] args) { 12 13 final ListAdd2 list2 = new ListAdd2(); 14 final byte[] lock = new byte[0]; 15 Thread t1 = new Thread(new Runnable() { 16 @Override 17 public void run() { 18 try { 19 synchronized (lock) { 20 System.out.println("t1啟動.."); 21 for(int i = 0; i <10; i++){ 22 list2.add(); 23 System.out.println("當前線程:" + Thread.currentThread().getName() + "添加了一個元素.."); 24 Thread.sleep(500); 25 if(list2.size() == 5){ 26 System.out.println("已經(jīng)發(fā)出通知.."); 27 lock.notify(); 28 } 29 } 30 } 31 } catch (InterruptedException e) { 32 e.printStackTrace(); 33 } 34 35 } 36 }, "t1"); 37 38 Thread t2 = new Thread(new Runnable() { 39 @Override 40 public void run() { 41 synchronized (lock) { 42 System.out.println("t2啟動.."); 43 if(list2.size() != 5){ 44 try { 45 lock.wait(); 46 } catch (InterruptedException e) { 47 e.printStackTrace(); 48 } 49 } 50 System.out.println("當前線程:" + Thread.currentThread().getName() + "收到通知線程停止.."); 51 throw new RuntimeException(); 52 } 53 } 54 }, "t2"); 55 t2.start(); 56 t1.start(); 57 } 58 }這里首先創(chuàng)建了一個的byte[]對象lock,然后線程t1,t2使用synchronzied關(guān)鍵字同步lock對象。線程t1一直往list添加元素,當元素大小等于5的時候調(diào)用lock.notify()方法通知lock對象。線程t2在size不等于5的時候一直處于等待狀態(tài)。
這里使用byte[0]數(shù)組是因為JVM創(chuàng)建byte[0]所占用的空間比普通的object對象小,而花費的代價也最小。
運行結(jié)果如下:
看到這里可能會有疑問,為什么t1通知了t2線程運行而結(jié)果卻是t1先運行完后t2再運行。
說明如下:
1、wait() 和 notify()必須配合synchrozied關(guān)鍵字使用,無論是wait()還是notify()都需要首先獲取目標對象的一個監(jiān)聽器。
2、wait()釋放鎖,而notify()不釋放鎖。
?線程t2一開始處于wait狀態(tài),這時候釋放了鎖所以t1可以一直執(zhí)行,而t1在notify的時候并不會釋放鎖,所以t1還會繼續(xù)運行。?
?
知識拓展
現(xiàn)在我們來探討一下有界阻塞隊列的實現(xiàn)原理并模擬一下它的實現(xiàn) :
1、有界隊列顧名思義是有容器大小限制的
2、當調(diào)用put()方法時,如果此時容器的長度等于限定的最大長度,那么該方法需要阻塞直到隊列可以有空間容納下添加的元素
3、當調(diào)用take()方法時,如果此時容器的長度等于最小長度0,那么該方法需要阻塞直到隊列中有了元素能夠取出
4、put() 和 take()方法是需要協(xié)作的,能夠及時通知狀態(tài)進行插入和移除操作
根據(jù)以上阻塞隊列的幾個屬性,我們可以使用wait 和notify實現(xiàn)以下它的實現(xiàn)原理:
?
/*** 自定義大小的阻塞容器*/ public class MyQueue {//1、初始化容器private final LinkedList<Object> list = new LinkedList<>();//2、定義計數(shù)器private AtomicInteger count = new AtomicInteger(0);//3、設定容器的上限和下限private final int minSize = 0;private final int maxSize;//4、構(gòu)造器public MyQueue(int size) {this.maxSize = size;}//5、定義鎖對象private final Object lock = new Object();//6、阻塞增加方法public void put(Object obj) {synchronized (lock) {while (count.get() == this.maxSize) {try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}//加入元素 計數(shù)器累加 喚醒取數(shù)線程可以取數(shù) list.add(obj);count.incrementAndGet();lock.notify();System.out.println("新增的元素:" + obj);}}public Object take() {Object result = null;synchronized (lock) {while (count.get() == this.minSize) {try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}//移除元素 計數(shù)器遞減 喚醒添加的線程可以添加元素result = list.removeFirst();count.decrementAndGet();lock.notify();}return result;}public int getSize() {return this.count.get();}public static void main(String[] args) {final MyQueue myQueue = new MyQueue(5);myQueue.put("a");myQueue.put("b");myQueue.put("c");myQueue.put("d");myQueue.put("e");System.out.println("當前隊列長度:" + myQueue.getSize());Thread t1 = new Thread(new Runnable() {@Override public void run() {myQueue.put("f");myQueue.put("g");}}, "t1");t1.start();Thread t2 = new Thread(new Runnable() {@Override public void run() {Object obj = myQueue.take();System.out.println("移除的元素為:"+obj);Object obj2 = myQueue.take();System.out.println("移除的元素為:"+obj2);}},"t2");try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}t2.start();} }?
實現(xiàn)過程如下:
1、通過構(gòu)造器初始化指定容器的大小
2、程序內(nèi)部有一個AtomicInteger的計數(shù)器,當調(diào)用put()操作時此計數(shù)器加1;當調(diào)用take()方法時此計數(shù)器減1
3、在進行相應的take()和put()方法時會使用while判斷進行阻塞,會一直處于wait狀態(tài),并在可以進行操作的時候喚醒另外一個線程可以進行相應的操作。
4、將此代碼運行可以看到相應的效果。
?
?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/jianzh5/p/6116968.html
總結(jié)
以上是生活随笔為你收集整理的线程之间通信 等待(wait)和通知(notify)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mac 下开发golang 配置
- 下一篇: [转]ClassPath是什么