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());
}
}