五、多線程協作之等待喚醒機制

線程間協作

多線程開發中,線程往往都不是孤立的。一個線程往往需要多線程協作完成其待執行的任務。等待喚醒機制就是用來協調線程間的協作。例如:街邊的小吃店都是生產一份等銷售完再生產,這是典型的生產者消費者模式。下面用代碼實現這個場景。

等待喚醒機制的好處: 節省cpu。線程間通訊也可以通過輪詢的方式來檢查條件進行協作,但是會消耗大量cpu。用生產者/消費者模式舉例。在生產者生產的時候,消費者並不需要執行。如果不用等待喚醒機制,消費者只能輪詢監控生存者是否完成生產,消耗cpu。通過等待喚醒機制,可以在生產這生產時,消費者進入等待狀態,不消耗cpu。待生存者生產完成後再喚醒消費者。

wait/notify

內部鎖是通過wait/notify/notifyAll這三個方法實現等待喚醒。wait方法會是一個線程進入等待狀態,notify會隨機喚醒一個等待狀態的線程,notifyAll會喚醒所有等待的線程。

void notify() 
          喚醒在此對象監視器上等待的單個線程。 
 void notifyAll() 
          喚醒在此對象監視器上等待的所有線程。 
 void wait() 
          在其他線程調用此對象的 notify() 方法或 notifyAll() 方法前,導致當前線程等待。 
 void wait(long timeout) 
          在其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量前,導致當前線程等待。 
 void wait(long timeout, int nanos) 
          在其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者其他某個線程中斷當前線程,或者已超過某個實際時間量前,導致當前線程等待。

注意:

  1. wait/notify/notifyAll只能在內部鎖作用範圍內調用。
  2. wait/notify/notifyAll都是通過該內部鎖的鎖對象調用。
  3. wait會釋放鎖。

###### 生存者消費者模式代碼

package Demo1;

public class Product {

    private int id;

    private boolean flag;
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

   public synchronized  void product() throws InterruptedException {
        while (id<100) {
            
            if (flag) {
                wait();
            }
            //用id的變化模擬生產一個物品
            id++;
            System.out.println("product............id:" + id);
            //物品生產好後flag變成true並喚醒消費者
            flag = true;
            notify();
        }
    }

    public synchronized  void comsume() throws InterruptedException {
        while (id<100) {
            if (!flag) {
                wait();
            }
            System.out.println("comsume............id:" + id);
            flag = false;
            notify();
        }
    }


}

public class Consumer implements Runnable {

    private Product p;
    Consumer(Product p){
        this.p=p;
    }
    public void run(){
        try {
            p.comsume();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


public class Producer implements Runnable {

    private Product p;
    Producer(Product p){
        this.p=p;
    }
    public void run() {
        try {
            p.product();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Main {
     public static void main(String[] args) throws InterruptedException {

         Product product = new Product();
         Producer producer = new Producer(product);
         Consumer consumer = new Consumer(product);
         Thread thread = new Thread(producer);
         thread.start();
         new Thread(consumer).start();
         thread.join();
     }
}

結果如下,生產一個產品,再消費一個產品。

product............id:1
comsume............id:1
product............id:2
comsume............id:2
product............id:3
comsume............id:3
product............id:4
comsume............id:4
product............id:5
comsume............id:5

wait/notify 存在的問題

  1. 過早喚醒。因爲notify喚醒具有隨機性。在不確保一定能喚醒想要喚醒的線程時,必須使用notifyAll。notifyAll會將所有線程喚醒,有些線程過早被喚醒,浪費資源。例如: 如果個多生產者多消費者模式,由於notify喚醒線程的隨機性,有可能喚醒的都是生產者或消費者線程。這時需要notifyAll來實現喚醒。notifyAll存在的弊端就是會把所有等待線程喚醒,無論是生產者還是消費者。這個問題可以通過顯示鎖的Condition來解決。

顯示鎖的等待喚醒機制 await/signal

jdk1.5加入的Lock可以通過創建多個Condition對象來實現分組的等待和喚醒。

Condition的方法

void await() 
          造成當前線程在接到信號或被中斷之前一直處於等待狀態。 
 boolean await(long time, TimeUnit unit) 
          造成當前線程在接到信號、被中斷或到達指定等待時間之前一直處於等待狀態。 
 long awaitNanos(long nanosTimeout) 
          造成當前線程在接到信號、被中斷或到達指定等待時間之前一直處於等待狀態。 
 void awaitUninterruptibly() 
          造成當前線程在接到信號之前一直處於等待狀態。 
 boolean awaitUntil(Date deadline) 
          造成當前線程在接到信號、被中斷或到達指定最後期限之前一直處於等待狀態。 
 void signal() 
          喚醒一個等待線程。 
 void signalAll() 
          喚醒所有等待線程。

Condtion版本生存者/消費者模式

以下Product2類,其他類和上面的一致。

package Demo2;

import Demo1.Product;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Product2 {

    private int id;
    private boolean flag;
    private final Lock lock = new ReentrantLock();
    private final Condition pro = lock.newCondition();
    private final Condition coms = lock.newCondition();
    private int size;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public void product() throws InterruptedException {
        try {
            lock.lock();
            while (id < 100) {
                if (flag) {
                    //已生產一個產品,使用生產者的Condtion對象使生產者線程等待
                    pro.await();
                }
                System.out.println(String.format("product............id:%d" , ++id));
                flag = true;
                //使用消費者的Condition是消費者線程喚醒
                coms.signalAll();
            }
        } finally {
            lock.unlock();
        }
    }

    public void comsume() throws InterruptedException {
        try {
            lock.lock();
            while (id < 100) {
                if (!flag) {
                    coms.await();
                }
                System.out.println(String.format("consumer............id:%d" , id));
                flag = false;
                pro.signalAll();
            }
        }finally {
            lock.unlock();
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章