Condition

Condition

总结

传统上,我们可以通过synchronized关键字 + wait + notify/notifyAll来实现多个线程之间的协调与通信,整个过程都是由JVM来帮助
我们实现的;开发者无需(也是无法)了解底层的实现细节。

从JDK5开始并发包提供了Lock,Condition(await与signal/signalAll)来实现多个线程之间的协调与通信,整个过程都是由开发者来
控制的,而且相比于传统方式,更加灵活,功能也更加强大。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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 MyTest2 {
public static void main(String[] args) {
BoundedContainer boundedContainer = new BoundedContainer();
IntStream.range(0, 20).forEach(i -> new Thread(()->{
try {
boundedContainer.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start());
IntStream.range(0, 20).forEach(i -> new Thread(()->{
try {
boundedContainer.put("hello");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start());
}
}
class BoundedContainer {
private String[] elements = new String[10];
private Lock lock = new ReentrantLock();
private Condition notEmpty = lock.newCondition();
private Condition notFull = lock.newCondition();
//elements数组中已有的元素数量
private int elementCount;
private int putIndex;
private int takeIndex;
public void put(String element) throws InterruptedException {
lock.lock();
try {
while (this.elementCount == this.elements.length) {
notFull.await();
}
elements[putIndex] = element;
if (++putIndex == this.elements.length) {
putIndex = 0;
}
++elementCount;
System.out.println(" put method:" + Arrays.toString(elements));
notEmpty.signal();
}finally {
lock.unlock();
}
}
public String take() throws InterruptedException {
lock.lock();
try {
while (this.elementCount == 0) {
notEmpty.await();
}
String element = elements[takeIndex];
elements[takeIndex] = null;
if (++takeIndex == this.elements.length) {
takeIndex = 0;
}
--elementCount;
System.out.println("take method:" + Arrays.toString(elements));
notFull.signal();
return element;
}finally {
lock.unlock();
}
}
}

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.

Condition类似于将对象监视器方法(wait、notify和notifyAll)分解到不同的对象中,通过将它们与任意Lock实现结合使用,实现一个对象具有多个等待集合的效果。Lock代替synchronized方法和语句块的使用,Condition代替Object监视器方法的使用。

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.

Conditions(也称为条件队列或条件变量)提供了一种方法,让一个线程暂停执行(“等待”),直到另一个线程通知某个状态条件现在可能为true。因为对共享状态信息的访问发生在不同的线程中,所以必须保护它,所以某种形式的Lock与Condition相关联。等待条件提供的关键属性是它自动释放关联的锁并挂起当前线程,就像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.

一个Condition实例会被天然的绑定到lock上。要获取特定Lock实例锁对应的Condition实例,请使用其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方法。如果尝试在空缓冲区上执行take操作,则线程将阻塞,直到某个项可用为止;如果在一个完整的缓冲区上尝试put,那么线程将阻塞,直到空间可用为止。我们希望将put线程和take线程放在不同的等待集中,这样我们就可以优化在缓冲区中的项或空间可用时只通知单个线程。这可以通过使用两个条件实例来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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.

Condition实现可以提供与Object监视器方法不同的行为和语义,比如通知的保证顺序,或者在执行通知时不需要持有锁。如果实现提供了这种专门的语义,那么实现必须记录这些语义。

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.

请注意,Condition实例只是普通对象,它们本身可以用作synchronized语句块中的目标,并且可以调用它们自己的监视器等待和通知方法。获取Condition实例的监控器锁,或使用其监控器方法,与获取与该条件相关的锁或使用其等待和发送信号的方法没有指定的关系。为了避免混淆,建议您永远不要以这种方式使用条件实例,除非在它们自己的实现中。

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.

在等待条件时,通常允许出现“伪唤醒”,这是对底层平台语义的让步。这对大多数应用程序几乎没有实际影响,因为应该始终在循环中等待一个条件,测试正在等待的状态谓词。实现可以自由地消除虚假唤醒的可能性,但建议应用程序编程人员始终假设它们会发生,因此始终在循环中等待。

await()

Causes the current thread to wait until it is signalled or interrupted.
The lock associated with this Condition is atomically released and the current thread becomes disabled for thread scheduling purposes and lies dormant until one of four things happens:

导致当前线程等待,直到被signall或中断。

与此Condition相关的Lock被原子释放,当前线程出于线程调度的目的被禁用,并处于休眠状态,直到发生以下四种情况之一:

  • Some other thread invokes the signal method for this Condition and the current thread happens to be chosen as the thread to be awakened; or
  • Some other thread invokes the signalAll method for this Condition; or
  • Some other thread interrupts the current thread, and interruption of thread suspension is supported; or
  • A “spurious wakeup” occurs.

  • 其他一些线程调用该Condition下的signal方法,而当前线程被选择为要唤醒的线程;或

  • 其他一些线程调用signalAll方法为这个条件;或
  • 其他线程中断当前线程,支持中断线程暂停;或
  • 一个“虚假的觉醒”发生了。

In all cases, before this method can return the current thread must re-acquire the lock associated with this condition. When the thread returns it is guaranteed to hold this lock.
If the current thread:

在所有情况下,在此方法返回之前,当前线程必须重新获取与此condition关联的lock。当线程返回时,它保证持有这个锁。

如果当前线程:

  • has its interrupted status set on entry to this method; or
  • is interrupted while waiting and interruption of thread suspension is supported,

  • 在进入此方法时已设置其中断状态;或

  • 在等待时中断,支持线程暂停中断,

then InterruptedException is thrown and the current thread’s interrupted status is cleared. It is not specified, in the first case, whether or not the test for interruption occurs before the lock is released.

然后抛出InterruptedException,并清除当前线程的中断状态。在第一种情况下,没有指定是否在释放锁之前进行中断测试。

awaitUninterruptibly()

awaitNanos(long nanosTimeout)

await(long time, TimeUnit unit)

awaitUntil(Date deadline)

signal()

Wakes up one waiting thread.

唤醒一个正在等待的线程。

If any threads are waiting on this condition then one is selected for waking up. That thread must then re-acquire the lock before returning from await.

如果有任何线程在此condition下等待,则选择一个线程进行唤醒。然后,该线程必须在从wait返回之前重新获取锁。

Implementation Considerations

实现注意事项

An implementation may (and typically does) require that the current thread hold the lock associated with this Condition when this method is called. Implementations must document this precondition and any actions taken if the lock is not held. Typically, an exception such as IllegalMonitorStateException will be thrown.

实现可能(通常是这样)要求当前线程在调用此方法时持有与此Condition关联的lock。实现必须记录此前提条件和在未持有锁时所采取的任何操作。通常,会抛出一个异常,如IllegalMonitorStateException。

signalAll()

Wakes up all waiting threads.

唤醒所有等待的线程。