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.

由于中断通常意味着取消,并且对中断的检查通常不频繁,因此实现可能更倾向于响应中断而不是正常的方法返回。即使可以显示中断发生在另一个可能已解除线程阻塞的操作之后,这也是正确的。实现应该记录这种行为。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

禅与计算机程序设计艺术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值