多线程(六)Condition 和 LockSupport

1、Condition

ConditionObject 监视器方法(waitnotifynotifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。

条件(也称为条件队列条件变量)为线程提供了一个含义,以便在某个状态条件现在可能为 true 的另一个线程通知它之前,一直挂起该线程(即让其“等待”)。因为访问此共享状态信息发生在不同的线程中,所以它必须受保护,因此要将某种形式的锁与该条件相关联。等待提供一个条件的主要属性是:以原子方式 释放相关的锁,并挂起当前线程,就像 Object.wait 做的那样。

Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法。

作为一个示例,假定有一个绑定的缓冲区,它支持 puttake 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待集合中保存 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) //count满了,notFull等待并释放锁,到另一个里面
         notFull.await();
       items[putptr] = x; 
       if (++putptr == items.length) putptr = 0;//尝试是否满了,满了的话,从0开始
       ++count;
       notEmpty.signal();    //通知notEmpty不用等了
     } finally {
       lock.unlock();    //释放锁
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0) //count没有,notEmpty等待并释放锁,到另一个里面
         notEmpty.await();
       Object x = items[takeptr]; 
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   } 
 }

和Object的wait()一样,在等待 Condition 时,允许发生“虚假唤醒”,这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为 Condition 应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。

public interface Condition {

/*    造成当前线程在接到信号或被中断之前一直处于等待状态。 
    与此 Condition 相关的锁以原子方式释放,并且使当前线程将一直处于休眠状态,直到: 
    (1)其他某个线程调用此 Condition 的 signal() 方法,并且碰巧将当前线程选为被唤醒的线程;或者 
    (2)其他某个线程调用此 Condition 的 signalAll() 方法;或者 
    (3)其他某个线程中断当前线程,且支持中断线程的挂起;或者 
    (4)发生“虚假唤醒” 
    在所有情况下,在此方法可以返回当前线程之前,都必须重新获取与此条件有关的锁。
    在线程返回时,可以保证 它保持此锁。 
    注意:如果当前线程: 
    (1)在进入此方法时已经设置了该线程的中断状态;或者 
    (2)在支持等待和中断线程挂起时,线程被中断, 
    则抛出 InterruptedException,并清除当前线程的中断状态。
    在第一种情况下,没有指定是否在释放锁之前发生中断测试。 
     */
    void await() throws InterruptedException;

/*    造成当前线程在接到信号之前一直处于等待状态。 
    与此条件相关的锁以原子方式释放,并使当前线程将一直处于休眠状态,直到: 
    (1)其他某个线程调用此 Condition 的 signal() 方法,并且碰巧将当前线程选为被唤醒的线程;或者 
    (2)其他某个线程调用此 Condition 的 signalAll() 方法;或者 
    (3)发生“虚假唤醒” 
    在所有情况下,在此方法可以返回当前线程之前,都必须重新获取与此条件有关的锁。
    在线程返回时,可以保证 它保持此锁。 
    注意:
    (1)如果在进入此方法时设置了当前线程的中断状态,或者
    (2)在等待时,线程被中断,
    那么在接到信号之前,它将继续等待。当最终从此方法返回时,仍然将设置其中断状态。 
     */
    void awaitUninterruptibly();

/*    造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。 
    与此条件相关的锁以原子方式释放,并且使当前线程将一直处于休眠状态,直到: 
    (1)其他某个线程调用此 Condition 的 signal() 方法,并且碰巧将当前线程选为被唤醒的线程;或者 
    (2)其他某个线程调用此 Condition 的 signalAll() 方法;或者 
    (3)其他某个线程中断当前线程,且支持中断线程的挂起;或者 
    (4)已超过指定的等待时间;或者 
    (5)发生“虚假唤醒”。 
    在所有情况下,在此方法可以返回当前线程之前,都必须重新获取与此条件有关的锁。
    在线程返回时,可以保证 它保持此锁。 
    注意:如果当前线程: 
    (1)在进入此方法时已经设置了该线程的中断状态;或者 
    (2)在支持等待和中断线程挂起时,线程被中断, 
    则抛出 InterruptedException,并且清除当前线程的已中断状态。
    在第一种情况下,没有指定是否在释放锁之前发生中断测试。 
    在返回时,该方法返回了所剩毫微秒数的一个估计值,以等待所提供的 nanosTimeout 值的时间,
    如果超时,则返回一个小于等于 0 的值。可以用此值来确定在等待返回但某一等待条件仍不具备的情况下,
    是否要再次等待,以及再次等待的时间。此方法的典型用法采用以下形式: 

 synchronized boolean aMethod(long timeout, TimeUnit unit) {
   long nanosTimeout = unit.toNanos(timeout);
   while (!conditionBeingWaitedFor) {
     if (nanosTimeout > 0)
         nanosTimeout = theCondition.awaitNanos(nanosTimeout);
      else
        return false;
   }
   // ... 
 }
     设计注意事项:此方法需要一个 nanosecond 参数,以避免在报告剩余时间时出现截断错误。
    在发生重新等待时,这种精度损失使得程序员难以确保总的等待时间不少于指定等待时间。 
     */
    long awaitNanos(long nanosTimeout) throws InterruptedException;

/*    造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。此方法在行为上等效于:
   awaitNanos(unit.toNanos(time)) > 0
    time - 最长等待时间,unit - time 参数的时间单位 
     */
    boolean await(long time, TimeUnit unit) throws InterruptedException;

/*    造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。 
    与此条件相关的锁以原子方式释放,并且使当前线程将一直处于休眠状态,直到: 
    (1)其他某个线程调用此 Condition 的 signal() 方法,并且碰巧将当前线程选为被唤醒的线程;或者 
    (2)其他某个线程调用此 Condition 的 signalAll() 方法;或者 
    (3)其他某个线程中断当前线程,且支持中断线程的挂起;或者 
    (4)指定的最后期限到了;或者 
    (5)发生“虚假唤醒”。 
    在所有情况下,在此方法可以返回当前线程之前,都必须重新获取与此条件有关的锁。
    在线程返回时,可以保证 它保持此锁。 
    注意:如果当前线程: 
    (1)在进入此方法时已经设置了该线程的中断状态;或者 
    (2)在支持等待和中断线程挂起时,线程被中断, 
    则抛出 InterruptedException,并且清除当前线程的已中断状态。
    在第一种情况下,没有指定是否在释放锁之前发生中断测试。 
    返回值指示是否到达最后期限,使用方式如下: 

 synchronized boolean aMethod(Date deadline) {
   boolean stillWaiting = true;
   while (!conditionBeingWaitedFor) {
     if (stillWaiting)
         stillWaiting = theCondition.awaitUntil(deadline);
      else
        return false;
   }
   // ... 
 }
参数:deadline - 一直处于等待状态的绝对时间 
返回:如果在返回时已经到达最后期限,则返回 false,否则返回 true 
     */
    boolean awaitUntil(Date deadline) throws InterruptedException;

/*    唤醒一个等待线程。 
    如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await 返回之前,该线程必须重新获取锁。
     */
    void signal();

/*    唤醒所有等待线程。 
    如果所有的线程都在等待此条件,则唤醒所有线程。在从 await 返回之前,每个线程都必须重新获取锁。 
     */
    void signalAll();
}

2、LockSupport

 用来创建锁和其他同步类的基本线程阻塞原语。

此类以及每个使用它的线程与一个许可关联(从 Semaphore 类的意义上说)。如果该许可可用,并且可在进程中使用,则调用 park 将立即返回;否则可能 阻塞。如果许可尚不可用,则可以调用 unpark 使其可用。(但与 Semaphore 不同的是,许可不能累积,并且最多只能有一个许可。)

parkunpark 方法提供了阻塞和解除阻塞线程的有效方法,并且不会遇到导致过时方法 Thread.suspendThread.resume 因为以下目的变得不可用的问题:由于许可的存在,调用 park 的线程和另一个试图将其 unpark 的线程之间的竞争将保持活性。此外,如果调用者线程被中断,并且支持超时,则 park 将返回。park 方法还可以在其他任何时间“毫无理由”地返回,因此通常必须在重新检查返回条件的循环里调用此方法。从这个意义上说,park 是“忙碌等待”的一种优化,它不会浪费这么多的时间进行自旋,但是必须将它与 unpark 配对使用才更高效。

三种形式的 park 还各自支持一个 blocker 对象参数。此对象在线程受阻塞时被记录,以允许监视工具和诊断工具确定线程受阻塞的原因。(这样的工具可以使用方法 getBlocker(java.lang.Thread) 访问 blocker。)建议最好使用这些形式,而不是不带此参数的原始形式。在锁实现中提供的作为 blocker 的普通参数是 this

这些方法被设计用来作为创建高级同步实用工具的工具,对于大多数并发控制应用程序而言,它们本身并不是很有用。park 方法仅设计用于以下形式的构造:

while (!canProceed()) { ... LockSupport.park(this); }

在这里,在调用 park 之前,canProceed 和其他任何动作都不会锁定或阻塞。因为每个线程只与一个许可关联,park 的任何中间使用都可能干扰其预期效果。

示例用法。 以下是一个先进先出 (first-in-first-out) 非重入锁类的框架。

class FIFOMutex {
   private final AtomicBoolean locked = new AtomicBoolean(false);
   private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>();

   public void lock() {
     boolean wasInterrupted = false;
     Thread current = Thread.currentThread();
     waiters.add(current);
     // Block while not first in queue or cannot acquire lock
     while (waiters.peek() != current ||!locked.compareAndSet(false, true)) {
        LockSupport.park(this);
        if (Thread.interrupted()) // ignore interrupts while waiting
          wasInterrupted = true;
     }
     waiters.remove();
     if (wasInterrupted)          // reassert interrupt status on exit
        current.interrupt();
   }
   public void unlock() {
     locked.set(false);
     LockSupport.unpark(waiters.peek());
   }
 }

 下面是源码:

public class LockSupport {
    private LockSupport() {} // Cannot be instantiated.
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long parkBlockerOffset;

    static {
        try {
            parkBlockerOffset = unsafe.objectFieldOffset
                (java.lang.Thread.class.getDeclaredField("parkBlocker"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    //原子操作,设置阻塞
    private static void setBlocker(Thread t, Object arg) {
        unsafe.putObject(t, parkBlockerOffset, arg);
    }
/*    返回提供给最近一次尚未解除阻塞的 park 方法调用的 blocker 对象,
    如果该调用不受阻塞,则返回 null。
    返回的值只是一个瞬间快照,即由于未解除阻塞或者在不同的 blocker 对象上受阻而具有的线程。 
    返回:blocker*/
    public static Object getBlocker(Thread t) {
        if (t == null)
            throw new NullPointerException();
        return unsafe.getObjectVolatile(t, parkBlockerOffset);
    }
/*    如果给定线程的许可尚不可用,则使其可用。如果线程在 park 上受阻塞,则它将解除其阻塞状态。
    否则,保证下一次调用 park 不会受阻塞。如果给定线程尚未启动,则无法保证此操作有任何效果。 
    参数:thread - 要执行 unpark 操作的线程;该参数为 null 表示此操作没有任何效果。 */
    public static void unpark(Thread thread) {
        if (thread != null)
            unsafe.unpark(thread);
    }
/*    为了线程调度,在许可可用之前禁用当前线程。 
    如果许可可用,则使用该许可,并且该调用立即返回;
    否则,为线程调度禁用当前线程,使其处于休眠状态,直到: 
    (1)其他某个线程调用将当前线程作为目标调用 unpark;或者 
    (2)其他某个线程中断当前线程;或者 
    (3)该调用不合逻辑地(即毫无理由地)返回。 
    此方法不报告是哪个线程导致该方法返回。调用者应该重新检查最先导致线程暂停的条件。
    调用者还可以确定返回时该线程的中断状态。 
    参数:blocker - 导致此线程暂停的同步对象*/
    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        unsafe.park(false, 0L);
        setBlocker(t, null);
    }
    public static void park() {
        unsafe.park(false, 0L);
    }
/*    为了线程调度,在许可可用前禁用当前线程,并最多等待指定的等待时间。 
    参数:blocker - 导致此线程暂停的同步对象,nanos - 要等待的最大毫秒数
*/
    public static void parkNanos(Object blocker, long nanos) {
        if (nanos > 0) {
            Thread t = Thread.currentThread();
            setBlocker(t, blocker);
            unsafe.park(false, nanos);
            setBlocker(t, null);
        }
    }
    public static void parkNanos(long nanos) {
        if (nanos > 0)
            unsafe.park(false, nanos);
    }
/*    为了线程调度,在指定的时限前禁用当前线程,除非许可可用。 
    参数:blocker - 导致此线程暂停的同步对象,deadline - 要等待的绝对时间,用相对于历元 (Epoch) 的毫秒数值表示*/  
    public static void parkUntil(Object blocker, long deadline) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        unsafe.park(true, deadline);
        setBlocker(t, null);
    }
    public static void parkUntil(long deadline) {
        unsafe.park(true, deadline);
    }

}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值