1. 程式人生 > >java 執行緒 Lock 鎖使用Condition實現執行緒的等待(await)與通知(signal)

java 執行緒 Lock 鎖使用Condition實現執行緒的等待(await)與通知(signal)

轉自:小禾點點

一、Condition 類

  在前面我們學習與synchronized鎖配合的執行緒等待(Object.wait)與執行緒通知(Object.notify),那麼對於JDK1.5 的 java.util.concurrent.locks.ReentrantLock 鎖,JDK也為我們提供了與此功能相應的類java.util.concurrent.locks.Condition。Condition與重入鎖是通過lock.newCondition()方法產生一個與當前重入鎖繫結的Condtion例項,我們通知該例項來控制執行緒的等待與通知。該介面的所有方法:

public interface Condition {
     //使當前執行緒加入 await() 等待佇列中,並釋放當鎖,當其他執行緒呼叫signal()會重新請求鎖。與Object.wait()類似。
    void await() throws InterruptedException;

    //呼叫該方法的前提是,當前執行緒已經成功獲得與該條件物件繫結的重入鎖,否則呼叫該方法時會丟擲IllegalMonitorStateException。
    //呼叫該方法後,結束等待的唯一方法是其它執行緒呼叫該條件物件的signal()或signalALL()方法。等待過程中如果當前執行緒被中斷,該方法仍然會繼續等待,同時保留該執行緒的中斷狀態。 
    void awaitUninterruptibly();

    // 呼叫該方法的前提是,當前執行緒已經成功獲得與該條件物件繫結的重入鎖,否則呼叫該方法時會丟擲IllegalMonitorStateException。
    //nanosTimeout指定該方法等待訊號的的最大時間(單位為納秒)。若指定時間內收到signal()或signalALL()則返回nanosTimeout減去已經等待的時間;
    //若指定時間內有其它執行緒中斷該執行緒,則丟擲InterruptedException並清除當前執行緒的打斷狀態;若指定時間內未收到通知,則返回0或負數。 
    long awaitNanos(long nanosTimeout) throws InterruptedException;

    //與await()基本一致,唯一不同點在於,指定時間之內沒有收到signal()或signalALL()訊號或者執行緒中斷時該方法會返回false;其它情況返回true。
    boolean await(long time, TimeUnit unit) throws InterruptedException;

   //適用條件與行為與awaitNanos(long nanosTimeout)完全一樣,唯一不同點在於它不是等待指定時間,而是等待由引數指定的某一時刻。
    boolean awaitUntil(Date deadline) throws InterruptedException;
    
    //喚醒一個在 await()等待佇列中的執行緒。與Object.notify()相似
    void signal();

   //喚醒 await()等待佇列中所有的執行緒。與object.notifyAll()相似
    void signalAll();
}

二、使用

1、await()  等待  與 singnal()通知

 package com.jalja.org.base.Thread;
 
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.ReentrantLock;
 
 /**
  * Condition 配合Lock  實現執行緒的等待 與通知
  */
 public class ConditionTest{
     public static ReentrantLock lock=new ReentrantLock();
     public static Condition condition =lock.newCondition();
     public static void main(String[] args) {
         new Thread(){
             @Override
             public void run() {
                 lock.lock();//請求鎖
                 try{
                     System.out.println(Thread.currentThread().getName()+"==》進入等待");
                     condition.await();//設定當前執行緒進入等待
                 }catch (InterruptedException e) {
                     e.printStackTrace();
                 }finally{
                     lock.unlock();//釋放鎖
                 }
                 System.out.println(Thread.currentThread().getName()+"==》繼續執行");
             }    
         }.start();
         new Thread(){
             @Override
             public void run() {
                 lock.lock();//請求鎖
                 try{
                     System.out.println(Thread.currentThread().getName()+"=》進入");
                     Thread.sleep(2000);//休息2秒
                     condition.signal();//隨機喚醒等待佇列中的一個執行緒
                     System.out.println(Thread.currentThread().getName()+"休息結束");
                 }catch (InterruptedException e) {
                     e.printStackTrace();
                 }finally{
                     lock.unlock();//釋放鎖
                 }
             }    
         }.start();
     }
 }

執行結果:

Thread-0==》進入等待
Thread-1=》進入
Thread-1休息結束
Thread-0==》繼續執行

流程:在呼叫await()方法前執行緒必須獲得重入鎖(第17行程式碼),呼叫await()方法後執行緒會釋放當前佔用的鎖。同理在呼叫signal()方法時當前執行緒也必須獲得相應重入鎖(程式碼32行),呼叫signal()方法後系統會從condition.await()等待佇列中喚醒一個執行緒。當執行緒被喚醒後,它就會嘗試重新獲得與之繫結的重入鎖,一旦獲取成功將繼續執行。所以呼叫signal()方法後一定要釋放當前佔用的鎖(程式碼41行),這樣被喚醒的執行緒才能有獲得鎖的機會,才能繼續執行。

三、JDK中對Condition 的使用

  我們來看看java.util.concurrent.ArrayBlockingQueue;

  基於陣列的阻塞佇列實現,在ArrayBlockingQueue內部,維護了一個定長陣列,以便快取佇列中的資料物件,這是一個常用的阻塞佇列,除了一個定長陣列外,ArrayBlockingQueue內部還儲存著兩個整形變數,分別標識著佇列的頭部和尾部在陣列中的位置。

  看看他的put方法:

public void put(E e) throws InterruptedException {
        checkNotNull(e);//對傳入元素的null判斷
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();//對put()方法做同步
        try {
            while (count == items.length)//如果佇列已滿
                notFull.await();//讓當前新增元素的執行緒進入等待狀態
            insert(e);// 如果有其他執行緒呼叫signal() 通知該執行緒 ,則進行新增行為
        } finally {
            lock.unlock();//釋放鎖
        }
    }
    
    private E extract() {
        final Object[] items = this.items;
        E x = this.<E>cast(items[takeIndex]);
        items[takeIndex] = null;
        takeIndex = inc(takeIndex);
        --count;
        notFull.signal();//喚醒一個在Condition等待佇列中的執行緒
        return x;
    }