1. 程式人生 > >Java利用wait和notify實現執行緒間通訊

Java利用wait和notify實現執行緒間通訊

       Java的Object類提供了wait和notify方法用於實現執行緒間通訊(因為所有的java類都繼承了Object類,所以所有的java類都有這兩個方法)。這兩個方法在Object類中籤名如下:

public final native void wait(long timeout) throws InterruptedException;

/**
*喚醒一個等待此物件(notify方法所屬的物件)監視器(monitor)的執行緒。如果有多個*執行緒等待此物件的監視器,它們中的一個會被選中喚醒。至於選擇哪個執行緒,是任意的,由*具體的底層實現決定。一個執行緒通過呼叫此物件的wait方法來等待此物件的監視器。
*被喚醒的執行緒不是立刻就能接著執行的,而是要等待當前佔有此物件監視器的執行緒(一般就*是執行notify的執行緒走出同步方法後,釋放鎖)釋放此物件的鎖。
*/
public final native void notify();

       需要說明的是,wait和notify方法都必須配合synchronized關鍵字使用。只有進入了synchronized方法/程式碼塊,拿到了物件鎖之後,才能呼叫該鎖物件的wait或notify方法。執行鎖物件的wait方法後會釋放掉鎖,並阻塞住。這樣別的執行緒才有機會獲取物件鎖,從而才有機會執行synchronized方法/程式碼塊中的鎖物件的notify方法。
       如果沒有拿到鎖物件的鎖,就呼叫鎖物件的wait或notify方法,會報錯java.lang.IllegalMonitorStateException

       下面通過用LinkedList和wait、notify模擬一個佇列實現:

package com.pilaf.test;

import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @description:利用wait和notify方法模擬實現一個阻塞佇列
 * @author: pilaf
 * @create: 2018-10-28 20:52
 */
public class MyQueue
<T> { /** * 內部盛放元素的容器 */ private final LinkedList<T> list = new LinkedList<T>(); /** * 鎖物件 */ private Object lock = new Object(); /** * 佇列元素個數計數器 */ private volatile AtomicInteger count = new AtomicInteger(); /** * 佇列最大容量 */ private final int size ; public MyQueue(int initialSize) { this.size = initialSize; } public MyQueue() { this(Integer.MAX_VALUE); } /** * 往佇列中放入元素 * @param element * @throws InterruptedException */ public void put(T element) throws InterruptedException{ //獲取鎖 synchronized (lock) { //如果佇列滿了,釋放掉鎖,阻塞住 if (count.get() == size) { lock.wait(); } //能夠走到這一步說明佇列還有空間,把元素放入佇列中 list.add(element); //增加佇列元素計數器值 count.incrementAndGet(); //當容器空的時候,其他執行緒呼叫佇列pop方法的時候會阻塞在wait方法後,當put進新元素的時候去喚醒被阻塞的執行緒 lock.notify(); } } /** * 從佇列取一個元素 * @return * @throws InterruptedException */ public T pop() throws InterruptedException{ T result = null; synchronized (lock) { //容器為空 if(count.get()==0){ //釋放lock,阻塞住 lock.wait(); } //能走到這一步說明內部容器list不為空,返回第一個元素 result = list.removeFirst(); //容器元素個數計數器減一 count.decrementAndGet(); //當容器滿的時候,別的執行緒阻塞在put方法,這兒取走一個元素後喚醒其他阻塞在put的執行緒 lock.notify(); return result; } } }
package com.pilaf.test;

/**
 * @description:
 * @author: pilaf
 * @create: 2018-10-28 21:55
 */
public class MyQueueTest {

    public static void main(String[] args) {
    	//初始化一個容量為4的佇列
        MyQueue<Integer> myQueue = new MyQueue<Integer>(4);
        new Thread(new PutThread(myQueue)).start();
        new Thread(new PopThread(myQueue)).start();

    }

    static class PutThread implements Runnable {
        private MyQueue<Integer> myQueue;

        public PutThread(MyQueue<Integer> myQueue) {
            this.myQueue = myQueue;
        }

        public void run() {
            for(int i = 0;i<100;i++) {
                try {
                    myQueue.put(i);
                    System.out.println("put " + i);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    static class PopThread implements Runnable {
        private MyQueue<Integer> myQueue;

        public PopThread(MyQueue<Integer> myQueue) {
            this.myQueue = myQueue;
        }

        public void run() {
            while (true) {
                try {
                    Integer i = myQueue.pop();
                    System.out.println("pop " + i);
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

       讀者可以調整PutThread類中run方法中的睡眠時間和PopThread類中run方法中的睡眠時間,讓兩個執行緒的睡眠時間有長有短可以看到一個執行緒快速往佇列裡放元素或取元素的時候會阻塞住,而一旦慢的執行緒取走或者放入元素,快的執行緒又能立馬放入或取走元素。