JUC 之 ConditionObject 詳解

源代碼

package i.juc;

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

/**
 * @author: Jack
 * 2020-05-28 15:12
 */
public class BoundedBuffer {

    ReentrantLock lock = new ReentrantLock();

    Condition canPut = lock.newCondition();
    Condition canTake = lock.newCondition();

    /**
     * 存儲隊列的元素
     */
    Item[] items = new Item[10];
    /**
     * 當前進隊列的下標
     */
    int putptr;
    /**
     * 當前出隊列的下標
     */
    int takeptr;
    /**
     * 元素個數
     */
    int count;

    public static void main(String[] args) {

        BoundedBuffer boundedBuffer = new BoundedBuffer();


        new Thread(() -> {
            try {
                int i = 0;
                while (true) {
                    Item obj = new Item((i++) + "");
                    boundedBuffer.put(obj);
                    boundedBuffer.println("Input:" + obj);
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                while (true) {
                    Item item = boundedBuffer.take();
                    boundedBuffer.println("Take:" + item);
                    boundedBuffer.printItems();
                    Thread.sleep(3000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

    }

    public synchronized void printItems() {
        System.out.println("----------------------------------------------");
        for (Item i : items) {
            System.out.print(i + "\t");
        }
        System.out.println();
        System.out.println("----------------------------------------------");
    }

    public synchronized void println(Object o) {
        System.out.println(o);
    }

    /**
     * 如果在一個完整的緩衝區上嘗試put,那麼線程將阻塞,直到空間可用爲止。
     *
     * @param e
     */
    void put(Item e) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length) {
                canPut.await();
            }

            items[putptr] = e;
            count++;

            // 如果 put 下標位置超出了 items 的長度,就重新從 0 開始 put (Bounded)。
            if (++putptr == items.length) {
                putptr = 0;
            }
            canTake.signal();

        } finally {
            lock.unlock();
        }
    }

    /**
     * 如果在空緩衝區上(count=0)嘗試獲取,那麼線程將阻塞,直到某個項可用爲止
     *
     * @return
     */
    Item take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0) {
                canTake.await();
            }

            Item x = items[takeptr];
            --count;

            // 如果出隊列的下標超出了 items 的長度,takeptr 就回到 0
            if (++takeptr == items.length) {
                takeptr = 0;
            }

            // 出隊列 1 個,就可以入隊列了
            canPut.signal();

            return x;

        } finally {
            lock.unlock();
        }
    }

}

class Item {
    String name;

    public Item(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "E" + name;
    }
}

運行結果:

Input:E0
Take:E0
----------------------------------------------
E0  null    null    null    null    null    null    null    null    null    
----------------------------------------------
Input:E1
Input:E2
Take:E1
----------------------------------------------
E0  E1  E2  null    null    null    null    null    null    null    
----------------------------------------------
Input:E3
Input:E4
Input:E5
Take:E2
----------------------------------------------
E0  E1  E2  E3  E4  E5  null    null    null    null    
----------------------------------------------
Input:E6
Input:E7
Input:E8
Take:E3
----------------------------------------------
E0  E1  E2  E3  E4  E5  E6  E7  E8  null    
----------------------------------------------
Input:E9
Input:E10
Input:E11
Take:E4
----------------------------------------------
E10 E11 E2  E3  E4  E5  E6  E7  E8  E9  
----------------------------------------------
Input:E12
Input:E13
Input:E14
Take:E5
----------------------------------------------
E10 E11 E12 E13 E14 E5  E6  E7  E8  E9  
----------------------------------------------
Input:E15
Take:E6
----------------------------------------------
E10 E11 E12 E13 E14 E15 E16 E7  E8  E9  
----------------------------------------------
Input:E16
Take:E7
----------------------------------------------
E10 E11 E12 E13 E14 E15 E16 E17 E8  E9  
----------------------------------------------
Input:E17
Take:E8
----------------------------------------------
E10 E11 E12 E13 E14 E15 E16 E17 E18 E9  
----------------------------------------------
Input:E18
Take:E9
----------------------------------------------
E10 E11 E12 E13 E14 E15 E16 E17 E18 E19 
----------------------------------------------
Input:E19
Take:E10
----------------------------------------------
E20 E11 E12 E13 E14 E15 E16 E17 E18 E19 
----------------------------------------------
Input:E20
Take:E11
----------------------------------------------
E20 E21 E12 E13 E14 E15 E16 E17 E18 E19 
----------------------------------------------
Input:E21
Take:E12
----------------------------------------------
E20 E21 E22 E13 E14 E15 E16 E17 E18 E19 
----------------------------------------------
Input:E22

代碼講解

package java.util.concurrent.locks;

public interface Condition {
    void await() throws InterruptedException;
    void awaitUninterruptibly();
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    boolean awaitUntil(Date deadline) throws InterruptedException;
    void signal();
    void signalAll();
}

Condition factors out the Object monitor methods (wait, notify and notifyAll) into distinct objects to give the effect of having multiple wait-sets per object, by combining them with the use of arbitrary Lock implementations. Where a Lock replaces the use of synchronized methods and statements, a Condition replaces the use of the Object monitor methods.

條件將對象監視方法(wait、notify和notifyAll)分解爲不同的對象,通過將它們與任意鎖實現結合使用,實現每個對象具有多個等待集的效果。鎖代替同步方法和語句的使用,條件代替對象監視器方法的使用。

Conditions (also known as condition queues or condition variables) provide a means for one thread to suspend execution (to "wait") until notified by another thread that some state condition may now be true. Because access to this shared state information occurs in different threads, it must be protected, so a lock of some form is associated with the condition. The key property that waiting for a condition provides is that it atomically releases the associated lock and suspends the current thread, just like Object.wait.

條件(也稱爲條件隊列或條件變量)提供了一種方法,讓一個線程暫停執行(“等待”),直到另一個線程通知某個狀態條件現在可能爲真。因爲對共享狀態信息的訪問發生在不同的線程中,所以必須對其進行保護,所以某種形式的鎖與條件相關聯。等待條件提供的關鍵屬性是它自動釋放相關鎖並掛起當前線程,就像Object.wait一樣。

A Condition instance is intrinsically bound to a lock. To obtain a Condition instance for a particular Lock instance use its newCondition() method.

條件實例本質上綁定到鎖。要獲取特定鎖實例的條件實例,請使用其newCondition()方法。

As an example, suppose we have a bounded buffer which supports put and take methods. If a take is attempted on an empty buffer, then the thread will block until an item becomes available; if a put is attempted on a full buffer, then the thread will block until a space becomes available. We would like to keep waiting put threads and take threads in separate wait-sets so that we can use the optimization of only notifying a single thread at a time when items or spaces become available in the buffer. This can be achieved using two Condition instances.

例如,假設我們有一個有界的緩衝區,它支持put和take方法。如果在空緩衝區上嘗試獲取,那麼線程將阻塞,直到某個項可用爲止;如果在一個完整的緩衝區上嘗試put,那麼線程將阻塞,直到空間可用爲止。我們希望put線程和take線程在獨立的wait-sets中保持等待,這樣我們就可以在緩衝區中的items或空間可用時,只通知單個線程。這可以通過使用兩個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();
       }
     }
   }
   

(The java.util.concurrent.ArrayBlockingQueue class provides this functionality, so there is no reason to implement this sample usage class.)

A Condition implementation can provide behavior and semantics that is different from that of the Object monitor methods, such as guaranteed ordering for notifications, or not requiring a lock to be held when performing notifications. If an implementation provides such specialized semantics then the implementation must document those semantics.

條件實現可以提供與對象監視器方法不同的行爲和語義,例如保證通知的順序,或者在執行通知時不需要持有鎖。如果實現提供了這種專門的語義,那麼實現必須記錄這些語義。

Note that Condition instances are just normal objects and can themselves be used as the target in a synchronized statement, and can have their own monitor wait and notification methods invoked.

請注意,條件實例只是普通的對象,它們本身可以用作同步語句中的目標,並且可以調用它們自己的監視器等待和通知方法。

Acquiring the monitor lock of a Condition instance, or using its monitor methods, has no specified relationship with acquiring the Lock associated with that Condition or the use of its waiting and signalling methods. It is recommended that to avoid confusion you never use Condition instances in this way, except perhaps within their own implementation.

Except where noted, passing a null value for any parameter will result in a NullPointerException being thrown.

獲取條件實例的監控器鎖,或使用其監控器方法,與獲取與該條件關聯的鎖或使用其等待和發送信號方法沒有指定的關係。爲了避免混淆,建議您永遠不要以這種方式使用條件實例,除非在它們自己的實現中使用。

除非特別指出,爲任何參數傳遞null值都會導致拋出NullPointerException。

Implementation Considerations

When waiting upon a Condition, a "spurious wakeup" is permitted to occur, in general, as a concession to the underlying platform semantics(底層平臺語義). This has little practical impact on most application programs as a Condition should always be waited upon in a loop, testing the state predicate that is being waited for.

在等待條件時,通常允許出現“僞喚醒”,作爲對底層平臺語義的讓步。這對大多數應用程序幾乎沒有實際影響,因爲應該始終在循環中等待一個條件,測試正在等待的狀態謂詞。

An implementation is free to remove the possibility of spurious wakeups but it is recommended that applications programmers always assume that they can occur and so always wait in a loop.

實現可以自由地消除虛假喚醒的可能性,但建議應用程序編程人員始終假設它們會發生,因此始終在循環中等待。

The three forms of condition waiting (interruptible, non-interruptible, and timed) may differ in their ease of implementation on some platforms and in their performance characteristics. In particular, it may be difficult to provide these features and maintain specific semantics such as ordering guarantees. Further, the ability to interrupt the actual suspension of the thread may not always be feasible to implement on all platforms.

三種形式的條件等待(可中斷的、不可中斷的和定時的)在某些平臺上的實現便利性和性能特徵上可能有所不同。特別是,提供這些特性和維護特定的語義(如排序保證)可能比較困難。此外,中斷線程的實際掛起的能力可能並不總是能夠在所有平臺上實現。

Consequently, an implementation is not required to define exactly the same guarantees or semantics for all three forms of waiting, nor is it required to support interruption of the actual suspension of the thread.

因此,實現不需要爲所有三種形式的等待定義完全相同的保證或語義,也不需要支持中斷線程的實際掛起。

An implementation is required to clearly document the semantics and guarantees provided by each of the waiting methods, and when an implementation does support interruption of thread suspension then it must obey the interruption semantics(中斷語義) as defined in this interface.

實現需要清楚地記錄每個等待方法提供的語義和保證,當實現支持中斷線程暫停時,它必須遵守此接口中定義的中斷語義。

As interruption generally implies cancellation, and checks for interruption are often infrequent, an implementation can favor responding to an interrupt over normal method return. This is true even if it can be shown that the interrupt occurred after another action that may have unblocked the thread. An implementation should document this behavior.

由於中斷通常意味着取消,並且對中斷的檢查通常不頻繁,因此實現可能更傾向於響應中斷而不是正常的方法返回。即使可以顯示中斷髮生在另一個可能已解除線程阻塞的操作之後,這也是正確的。實現應該記錄這種行爲。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章