Java併發學習筆記7 Condition

bilibili-Java併發學習筆記7 Condition

基於 java 1.8.0

P27_Condition詳解及相比於傳統線程併發模式的改進

public interface Lock {
    /**
     * 返回綁定到此 Lock 實例的新 Condition 實例。
     *
     * 在等待條件前,鎖必須由當前線程保持。調用 Condition.await() 將在等待前以原子方式釋放鎖,並在等待返回前重新獲取鎖。
     *
     * Condition 實例的具體操作依賴於 Lock 實現,並且該實現必須對此加以記錄。
     *
     * @return 用於此 Lock 實例的新 Condition 實例
     * @throws UnsupportedOperationException 如果此 Lock 實現不支持條件
     */
    Condition newCondition();
}
/**
 * Condition 將 Object 監視器方法(wait、notify 和 notifyAll)分解成截然不同的對象,
 * 以便通過將這些對象與任意 Lock 實現組合使用,爲每個對象提供`多個`等待 set(wait-set)。
 * 其中,Lock 替代了 synchronized 方法和語句的使用,Condition 替代了 Object 監視器方法的使用。
 *
 * 條件(也稱爲條件隊列 或條件變量)爲線程提供了一個含義,以便在某個狀態條件現在可能爲 true 的
 * 另一個線程通知它之前,一直掛起該線程(即讓其“等待”)。因爲訪問此共享狀態信息發生在不同的線程中,
 * 所以它必須受保護,因此要將某種形式的鎖與該條件相關聯。等待提供一個條件的主要屬性是:以原子方式
 * 釋放相關的鎖,並掛起當前線程,就像 Object.wait 做的那樣。
 *
 * Condition 實例實質上被綁定到一個鎖上。要爲特定 Lock 實例獲得 Condition 實例,
 * 請使用其 newCondition() 方法。
 *
 * 作爲一個示例,假定有一個綁定的緩衝區,它支持 put 和 take 方法。如果試圖在空的緩衝區上執行 take 操作,
 * 則在某一個項變得可用之前,線程將一直阻塞;如果試圖在滿的緩衝區上執行 put 操作,則在有空間變得可用之前,
 * 線程將一直阻塞。我們喜歡在單獨的等待 set 中保存 put 線程和 take 線程,這樣就可以在緩衝區中的項或
 * 空間變得可用時利用最佳規劃,一次只通知一個線程。可以使用兩個 Condition 實例來做到這一點。
 *
 * class BoundedBuffer {
 *   final Lock lock = new ReentrantLock();
 *   final Condition notFull  = lock.newCondition();
 *   final Condition notEmpty = lock.newCondition();
 *
 *   final Object[] items = new Object[100];
 *   int putptr, takeptr, count;
 *
 *   public void put(Object x) throws InterruptedException {
 *     lock.lock();
 *     try {
 *       while (count == items.length)
 *         notFull.await();
 *       items[putptr] = x;
 *       if (++putptr == items.length) putptr = 0;
 *       ++count;
 *       notEmpty.signal();
 *     } finally {
 *       lock.unlock();
 *     }
 *   }
 *
 *   public Object take() throws InterruptedException {
 *     lock.lock();
 *     try {
 *       while (count == 0)
 *         notEmpty.await();
 *       Object x = items[takeptr];
 *       if (++takeptr == items.length) takeptr = 0;
 *       --count;
 *       notFull.signal();
 *       return x;
 *     } finally {
 *       lock.unlock();
 *     }
 *   }
 * }
 *
 * (ArrayBlockingQueue 類提供了這項功能,因此沒有理由去實現這個示例類。)
 *
 * Condition 實現可以提供不同於 Object 監視器方法的行爲和語義,比如受保證的通知排序,
 * 或者在執行通知時不需要保持一個鎖。如果某個實現提供了這樣特殊的語義,則該實現必須記錄這些語義。
 *
 * 注意,Condition 實例只是一些普通的對象,它們自身可以用作 synchronized 語句中的目標,
 * 並且可以調用自己的 wait 和 notification 監視器方法。獲取 Condition 實例的監視器鎖或者使用其監視器方法,
 * 與獲取和該 Condition 相關的 Lock 或使用其 waiting 和 signalling 方法沒有什麼特定的關係。
 * 爲了避免混淆,建議除了在其自身的實現中之外,切勿以這種方式使用 Condition 實例。
 *
 * 除非另行說明,否則爲任何參數傳遞 null 值將導致拋出 NullPointerException。
 *
 * 實現注意事項
 *
 * 在等待 Condition 時,允許發生“虛假喚醒”,這通常作爲對基礎平臺語義的讓步。對於大多數應用程序,
 * 這帶來的實際影響很小,因爲 Condition 應該總是在一個循環中被等待,並測試正被等待的狀態聲明。
 * 某個實現可以隨意移除可能的虛假喚醒,但建議應用程序程序員總是假定這些虛假喚醒可能發生,因此總是在一個循環中等待。
 *
 * 三種形式的條件等待(可中斷、不可中斷和超時)在一些平臺上的實現以及它們的性能特徵可能會有所不同。
 * 尤其是它可能很難提供這些特性和維護特定語義,比如排序保證。更進一步地說,中斷線程實際掛起的能力在所有平臺上並不是總是可行的。
 *
 * 因此,並不要求某個實現爲所有三種形式的等待定義完全相同的保證或語義,也不要求其支持中斷線程的實際掛起。
 *
 * 要求實現清楚地記錄每個等待方法提供的語義和保證,在某個實現不支持中斷線程的掛起時,它必須遵從此接口中定義的中斷語義。
 *
 * 由於中斷通常意味着取消,而又通常很少進行中斷檢查,因此實現可以先於普通方法的返回來對中斷進行響應。
 * 即使出現在另一個操作後的中斷可能會釋放線程鎖時也是如此。實現應記錄此行爲。
 *
 * @since 1.5
 * @author Doug Lea
 */
public interface Condition {

    /**
     * 造成當前線程在`接到信號`或`被中斷`之前一直處於等待狀態。
     *
     * 與此 Condition 相關的鎖以原子方式釋放,並且出於線程調度的目的,將禁用當前線程,且在發生以下四種情況之一 以前,當前線程將一直處於休眠狀態:
     * - 其他某個線程調用此 Condition 的 signal() 方法,並且碰巧將當前線程選爲被喚醒的線程;或者
     * - 其他某個線程調用此 Condition 的 signalAll() 方法;或者
     * - 其他某個線程中斷當前線程,且支持中斷線程的掛起;或者
     * - 發生“虛假喚醒”
     *
     * 在所有情況下,在此方法可以返回當前線程之前,都必須重新獲取與此條件有關的鎖。在線程返回時,可以保證 它保持此鎖。
     * 如果當前線程:
     * - 在進入此方法時已經設置了該線程的中斷狀態;或者
     * - 在支持等待和中斷線程掛起時,線程被中斷,
     *
     * 則拋出 InterruptedException,並清除當前線程的中斷狀態。在第一種情況下,沒有指定是否在釋放鎖之前發生中斷測試。
     *
     * 實現注意事項
     *
     * 假定調用此方法時,當前線程保持了與此 Condition 有關聯的鎖。這取決於確定是否爲這種情況以及不是時,如何對此作出響應的實現。
     * 通常,將拋出一個異常(比如 IllegalMonitorStateException)並且該實現必須對此進行記錄。
     *
     * 與響應某個信號而返回的普通方法相比,實現可能更喜歡響應某個中斷。在這種情況下,實現必須確保信號被重定向到另一個等待線程(如果有的話)。
     *
     * @throws InterruptedException 如果當前線程被中斷(並且支持中斷線程掛起)
     */
    void await() throws InterruptedException;

    /**
     * 造成當前線程在接到信號之前一直處於等待狀態。
     *
     * 與此條件相關的鎖以原子方式釋放,並且出於線程調度的目的,將禁用當前線程,且在發生以下三種情況之一 以前,當前線程將一直處於休眠狀態:
     * - 其他某個線程調用此 Condition 的 signal() 方法,並且碰巧將當前線程選爲被喚醒的線程;或者
     * - 其他某個線程調用此 Condition 的 signalAll() 方法;或者
     * - 發生“虛假喚醒”
     *
     * 在所有情況下,在此方法可以返回當前線程之前,都必須重新獲取與此條件有關的鎖。在線程返回時,可以保證 它保持此鎖。
     * 如果在進入此方法時設置了當前線程的中斷狀態,或者在等待時,線程被中斷,那麼在接到信號之前,它將繼續等待。當最終從此方法返回時,仍然將設置其中斷狀態。
     *
     * 假定調用此方法時,當前線程保持了與此 Condition 有關聯的鎖。這取決於確定是否爲這種情況以及不是時,如何對此作出響應的實現。
     * 通常,將拋出一個異常(比如 IllegalMonitorStateException)並且該實現必須對此進行記錄。
     */
    void awaitUninterruptibly();

    /**
     * 造成當前線程在接到信號、被中斷或到達指定等待時間之前一直處於等待狀態。
     *
     * 與此條件相關的鎖以原子方式釋放,並且出於線程調度的目的,將禁用當前線程,且在發生以下五種情況之一 以前,當前線程將一直處於休眠狀態:
     * - 其他某個線程調用此 Condition 的 signal() 方法,並且碰巧將當前線程選爲被喚醒的線程;或者
     * - 其他某個線程調用此 Condition 的 signalAll() 方法;或者
     * - 其他某個線程中斷當前線程,且支持中斷線程的掛起;或者
     * - 已超過指定的等待時間;或者
     * - 發生“虛假喚醒”。
     * 
     * 在所有情況下,在此方法可以返回當前線程之前,都必須重新獲取與此條件有關的鎖。在線程返回時,可以保證 它保持此鎖。
     *
     * 如果當前線程:
     * - 在進入此方法時已經設置了該線程的中斷狀態;或者
     * - 在支持等待和中斷線程掛起時,線程被中斷,
     *
     * 則拋出 InterruptedException,並且清除當前線程的已中斷狀態。在第一種情況下,沒有指定是否在釋放鎖之前發生中斷測試。
     *
     * 在返回時,該方法返回了所剩毫微秒數的一個估計值,以等待所提供的 nanosTimeout 值的時間,如果超時,則返回一個小於等於 0 的值。
     * 可以用此值來確定在等待返回但某一等待條件仍不具備的情況下,是否要再次等待,以及再次等待的時間。此方法的典型用法採用以下形式:
     *
     * boolean aMethod(long timeout, TimeUnit unit) {
     *   long nanos = unit.toNanos(timeout);
     *   lock.lock();
     *   try {
     *     while (!conditionBeingWaitedFor()) {
     *       if (nanos <= 0L)
     *         return false;
     *       nanos = theCondition.awaitNanos(nanos);
     *     }
     *     // ...
     *   } finally {
     *     lock.unlock();
     *   }
     * }
     *
     * 設計注意事項:此方法需要一個 nanosecond 參數,以避免在報告剩餘時間時出現截斷錯誤。在發生重新等待時,這種精度損失使得程序員難以確保總的等待時間不少於指定等待時間。
     *
     * 假定調用此方法時,當前線程保持了與此 Condition 有關聯的鎖。這取決於確定是否爲這種情況以及不是時,如何對此作出響應的實現。
     * 通常會拋出一個異常(比如 IllegalMonitorStateException)並且該實現必須對此進行記錄。
     *
     * 與響應某個信號而返回的普通方法相比,或者與指示所使用的指定等待時間相比,實現可能更喜歡響應某個中斷。在任意一種情況下,實現必須確保信號被重定向到另一個等待線程(如果有的話)。
     *
     * @param nanosTimeout 等待的最長時間,以毫微秒爲單位
     * @return nanosTimeout 值減去花費在等待此方法的返回結果的時間的估算。正值可以用作對此方法進行後續調用的參數,來完成等待所需時間結束。小於等於零的值表示沒有剩餘時間。
     * @throws InterruptedException 如果當前線程被中斷(並且支持中斷線程掛起)
     */
    long awaitNanos(long nanosTimeout) throws InterruptedException;

    /**
     * 造成當前線程在接到信號、被中斷或到達指定等待時間之前一直處於等待狀態。此方法在行爲上等效於:
     * awaitNanos(unit.toNanos(time)) > 0
     *
     * @param time 最長等待時間
     * @param unit time 參數的時間單位
     * @return 如果在從此方法返回前檢測到等待時間超時,則返回 false,否則返回 true
     * @throws InterruptedException 如果當前線程被中斷(並且支持中斷線程掛起)
     */
    boolean await(long time, TimeUnit unit) throws InterruptedException;

    /**
     * 造成當前線程在接到信號、被中斷或到達指定最後期限之前一直處於等待狀態。
     *
     * 與此條件相關的鎖以原子方式釋放,並且出於線程調度的目的,將禁用當前線程,且在發生以下五種情況之一 以前,當前線程將一直處於休眠狀態:
     * - 其他某個線程調用此 Condition 的 signal() 方法,並且碰巧將當前線程選爲被喚醒的線程;或者
     * - 其他某個線程調用此 Condition 的 signalAll() 方法;或者
     * - 其他某個線程中斷當前線程,且支持中斷線程的掛起;或者
     * - 指定的最後期限到了;或者
     * - 發生“虛假喚醒”。
     *
     * 在所有情況下,在此方法可以返回當前線程之前,都必須重新獲取與此條件有關的鎖。在線程返回時,可以保證 它保持此鎖。
     *
     * 如果當前線程:
     * - 在進入此方法時已經設置了該線程的中斷狀態;或者
     * - 在支持等待和中斷線程掛起時,線程被中斷,
     * 則拋出 InterruptedException,並且清除當前線程的已中斷狀態。在第一種情況下,沒有指定是否在釋放鎖之前發生中斷測試。
     *
     *
     * 返回值指示是否到達最後期限,使用方式如下:
     * boolean aMethod(Date deadline) {
     *   boolean stillWaiting = true;
     *   lock.lock();
     *   try {
     *     while (!conditionBeingWaitedFor()) {
     *       if (!stillWaiting)
     *         return false;
     *       stillWaiting = theCondition.awaitUntil(deadline);
     *     }
     *     // ...
     *   } finally {
     *     lock.unlock();
     *   }
     * }}
     *
     * 假定調用此方法時,當前線程保持了與此 Condition 有關聯的鎖。這取決於確定是否爲這種情況以及不是時,如何對此作出響應的實現。
     * 通常,將拋出一個異常(比如 IllegalMonitorStateException)並且該實現必須對此進行記錄。
     *
     * 與響應某個信號而返回的普通方法相比,或者與指示是否到達指定最終期限相比,實現可能更喜歡響應某個中斷。
     * 在任意一種情況下,實現必須確保信號被重定向到另一個等待線程(如果有的話)。
     *
     * @param deadline 一直處於等待狀態的絕對時間
     * @return 如果在返回時已經到達最後期限,則返回 false,否則返回 true
     * @throws InterruptedException 如果當前線程被中斷(並且支持中斷線程掛起)
     */
    boolean awaitUntil(Date deadline) throws InterruptedException;

    /**
     * 喚醒一個等待線程。
     *
     * 如果所有的線程都在等待此條件,則選擇其中的一個喚醒。
     * 在從 await 返回之前,該線程必須重新獲取鎖。
     */
    void signal();

    /**
     * 喚醒所有等待線程。
     *
     * 如果所有的線程都在等待此條件,則喚醒所有線程。
     * 在從 await 返回之前,每個線程都必須重新獲取鎖。
     */
    void signalAll();
}

  • 傳統上,通過 synchronized + Object(wait、notify/notifyAll) 來實現多個線程之間的通信和協調,整個過程都是由 JVM 來實現的,開發者無需瞭解底層細節
  • 從 JDK 5 開始,併發包提供了 Lock + Condition(await、signal/signalAll)來實現多個線程之間的通信和協調,這個過程都是由開發者來控制,相對來說更加靈活

P28_Condition編程模式詳解與分析

package new_package.thread.p28;

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

public class BoundedBuffer {
    final Lock lock = new ReentrantLock();
    final Condition notFull = lock.newCondition();
    final Condition notEmpty = lock.newCondition();

    final Object[] items = new Object[100];
    int putptr, takeptr, count;

    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length) {
                notFull.await();
            }
            items[putptr] = x;
            if (++putptr == items.length) {
                putptr = 0;
            }
            ++count;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0) {
                notEmpty.await();
            }
            Object x = items[takeptr];
            if (++takeptr == items.length) {
                takeptr = 0;
            }
            --count;
            notFull.signal();
            return x;
        } finally {
            lock.unlock();
        }
    }
}

P30_通過Condition實現線程間通信實例剖析

package new_package.thread.p28;

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

/**
 * 101010101010101010...
 */
public class ConditionTest {

    Lock lock = new ReentrantLock();
    Condition one = lock.newCondition();
    Condition zero = lock.newCondition();
    private int counter;

    public void incr() throws InterruptedException {
        lock.lock();
        try {
            while (counter > 0) {
                zero.await();
            }
            counter++;
            System.out.print(counter);
            one.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public void decr() throws InterruptedException {
        lock.lock();
        try {
            while (counter == 0) {
                one.await();
            }
            counter--;
            System.out.print(counter);
            zero.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ConditionTest conditionTest = new ConditionTest();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    conditionTest.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    conditionTest.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

P31_藉由Condition解決多線程通信問題實例剖析

P32_Condition對於多線程的處理深入剖析

package new_package.thread.p28;

import java.util.Arrays;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.IntStream;

public class BoundedContainer {
    final Lock lock = new ReentrantLock();
    final Condition notFull = lock.newCondition();
    final Condition notEmpty = lock.newCondition();

    final String[] elements = new String[10];
    private int putptr, takeptr;
    private int elementCount;

    public void put(String x) throws InterruptedException {
        lock.lock();
        try {
            while (elementCount == elements.length) {
                notFull.await();
            }
            elements[putptr] = x;
            if (++putptr == elements.length) {
                putptr = 0;
            }
            System.out.println("put :" + Arrays.asList(elements));
            ++elementCount;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public String take() throws InterruptedException {
        lock.lock();
        try {
            while (elementCount == 0) {
                notEmpty.await();
            }
            String x = elements[takeptr];
            elements[takeptr] = null;
            if (++takeptr == elements.length) {
                takeptr = 0;
            }
            System.out.println("take:" + Arrays.asList(elements));
            --elementCount;
            notFull.signal();
            return x;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        BoundedContainer container = new BoundedContainer();
        IntStream.range(0, 100).forEach(i -> new Thread(() -> {
            try {
                String element = container.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start());
        IntStream.range(0, 100).forEach(i -> new Thread(() -> {
            try {
                container.put("Hello:" + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start());
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章