Java并发编程:Condition接口

Java并发编程:阻塞队列ArrayBlockingQueue

Java并发编程:Lock接口

Java并发编程:浅谈ReentrantLock类

在阻塞队列ArrayBlockingQueue中有两个Condition类的对象notNull和notEmpty,他们俩是等待条件。就比如在阻塞添加时(put方法),一旦不满足添加条件,该线程会被notFull条件对象挂起(调用await方法)加到其等待队列中;如果能添加就调用了insert方法,insert方法通过notEmpty调用signal方法唤醒调用take()方法的线程,执行元素获取操作。当时我们就可以猜测到这几个方法的作用(await类似wait方法,signal类似notify方法),并且notFull中存放的是put线程,notNull中存放的是take线程,Condition底层也一定是某种容器来存放这些线程。

接下来我们来一探究竟

一、Condition接口

Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到Condition对象关联的锁。Condition对象是由Lock对象(调用Lock对象的newCondition()方法)创建出来的,换句话说,Condition是依赖Lock对象的。

一般都会将Condition对象作为成员变量。当调用await()方法后,当前线程会释放锁并在此等待,而其他线程调用Condition对象的signal()方法,通知当前线程后,当前线程才从await()方法返回,并且在返回前已经获取了锁。

/**
 * 当前线程进入等待状态直到被通知(signal)或中断,当前线程进入后台运行状态且从await()方法返回
 * 其他线程调用该Condition的signal或者signalAll方法,而当前线程被选中唤醒
 * 1、其他线程(interrupt)中断当前线程
 * 2、如果当前等待线程从await方法返回,那么表明当前线程已经获取了Condition对象的锁
 */
void await() throws InterruptedException;

/**
 * 当前线程进入等待状态直到被通知,对中断不响应
 */
void awaitUninterruptibly();  
     
/**
 * 当前线程进入等待状态直到被通知、中断或超时。返回值表示剩余时间,如果在nanosTimeout纳秒之前被唤
 * 醒,那么返回值就是nanosTimeout-实际耗时,返回值<=0说明超时
 */
long awaitNanos(long nanosTimeout) throws InterruptedException; 
 
boolean awaitUntil(Date deadline) throws InterruptedException;
  
/**
* 唤醒一个等待在Condition上的线程,该线程从等待方法返回之前必须获得与Condition相关联的锁
*/  
void signal(); 
    
void signalAll();

Condition的实现类是ConditionObject,存在于AbstractQueuedSynchronizer和AbstractQueuedLongSynchronizer中。

通过lock对象调用newCondition方法实则实例化了一个ConditionObject对象!

//ReentrantLock.class
public Condition newCondition() {
        return sync.newCondition();
}

//ReentrantLock$sync.class
public Condition newCondition() {
        return sync.newCondition();
}

//AbstractQueuedSynchronizer$ConditionObject
public ConditionObject() { }

二、ConditionObject实现类

1、成员属性

//头节点 
private transient Node firstWaiter;

//尾节点
private transient Node lastWaiter;

Condition内部维护了一个由Node节点组成的单向链表,包括头节点和尾节点,这个链表的作用是存放等待signal信号的线程,线程被封装为Node节点,Node中有两个指针分别指向前一个元素(prev)以及下一个元素(next)。一个Condition包含一个等待队列,当前线程调用Condition.await()方法,将会以当前线程构造节点,并将节点从尾部加入等待队列。

调用await()方法的线程必定是获取了锁的线程,也就是说该过程是由锁来保证线程安全的。在Object的监视器模型上,一个对象拥有一个同步队列和等待队列,而并发包中的Lock(更确切地说是同步器)拥有一个同步队列和多个等待队列,

阻塞队列ArrayBlockingQueue的结构就是如此。两个ConditionObject分别为notFull(put线程)和NotEmpty(take线程),并且在head与tail之间存放数据元素。

2、方法

Condition中最重要的两个方法await和signal

1)等待

先来看看await方法的源码:

public final void await() throws InterruptedException {
        if (Thread.interrupted())  // 线程被中断后直接抛出异常,所以在调用这个方法的时候最好使用try...catch
            throw new InterruptedException();
        Node node = addConditionWaiter();   // 将当前线程封装成一个Node节点,加入到链表的尾端
        int savedState = fullyRelease(node); // 释放当前线程占有的锁
        int interruptMode = 0;
        while (!isOnSyncQueue(node)) {  // 遍历同步队列,看当前节点是否在队列中,如果不在,说明节点还不能去获取锁
           LockSupport.park(this);  // 挂起当前线程
           if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
              break;
         }  
         if (acquireQueued(node, savedState) && interruptMode != THROW_IE)  // 当前线程被唤醒后,重新尝试去获取锁
              interruptMode = REINTERRUPT;
         if (node.nextWaiter != null) // clean up if cancelled
              unlinkCancelledWaiters();
         if (interruptMode != 0)
              reportInterruptAfterWait(interruptMode);
}

await方法中调用了addConditionWaiter方法:

private Node addConditionWaiter() {
       Node t = lastWaiter;  // 保存链表的尾节点
       // 如果t不是null && t.ws不是CONDITION,调用unlinkCancelledWaiters取消当前节点
       if (t != null && t.waitStatus != Node.CONDITION) {
            unlinkCancelledWaiters();
            t = lastWaiter;   // 重新保存尾节点
        }
       Node node = new Node(Thread.currentThread(), Node.CONDITION); // 把当前线程的ws设为CONDITION,封装成一个Node
       if (t == null)  // 如果t为null,表示链表为空,就把当前节点设为头节点
            firstWaiter = node;
       else            // t不为空,把t在condition队列上的next节点设为node
             t.nextWaiter = node;  
       lastWaiter = node;   // 改变引用,node设为尾节点
       return node;     // 返回该节点
}

把当前线程的状态设为CONDITION,封装成一个Node,把它添加到队列的尾部,如果当前队列尾节点不是CONDITION状态调用unlinkCancelledWaiters方法,从链表的头结点开始往后遍历,把状态不是CONDITION的节点全部移除。

调用await方法的线程成功获取了锁的线程,也就是同步队列中的首节点,该方法会将当前线程构造成节点并加入等待队列中,然后释放同步状态,唤醒同步队列中的后继节点,然后当前线程会进入等待状态。当等待队列中的节点被唤醒,则唤醒节点的线程开始尝试获取同步状态。如果不是通过其他线程调用Condition.signal()方法唤醒,而是对等待线程进行中断,则会抛出InterruptedException。

2)通知

调用Condition的signal()方法,将会唤醒在等待队列中等待时间最长的节点(首节点),在唤醒节点之前,会将节点移到同步队列中 

public final void signal() {
      if (!isHeldExclusively())
           throw new IllegalMonitorStateException();
      Node first = firstWaiter;
      if (first != null)
      doSignal(first);
}


private void doSignal(Node first) {
      do {
           if ( (firstWaiter = first.nextWaiter) == null)
                lastWaiter = null;
           first.nextWaiter = null;
      } while (!transferForSignal(first) &&
           (first = firstWaiter) != null);
}

调用该方法的前置条件是当前线程必须获取了锁,可以看到signal()方法进行了isHeldExclusively()检查,也就是当前线程必须是获取了锁的线程。接着获取等待队列的首节点,将其移动到同步队列并唤醒节点中的线程节点从等待队列移动到同步队列。

在doSingnal中调用了trasferForSignal,其中的enq方法描述了具体的指针移动:

final boolean transferForSignal(Node node) {
       
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
}


    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

通过调用同步器的enq(Node node)方法,等待队列中的头节点线程安全地移动到同步队列。当节点移动到同步队列后,当前线程被唤醒。被唤醒后的线程,将从await()方法中的while循环中退出(isOnSyncQueue(Node node)方法返回true,节点已经在同步队列中),进而调用同步器的acquireQueued()方法加入到获取同步状态的竞争中。成功获取同步状态(或者说锁)之后,被唤醒的线程将从先前调用的await()方法返回,此时该线程已经成功地获取了锁。Condition的signalAll()方法,相当于对等待队列中的每个节点均执行一次signal()方法,效果就是将等待队列中所有节点全部移动到同步队列中,并唤醒每个节点的线程。


参考资料

1、https://blog.csdn.net/fuyuwei2015/article/details/72602182

2、https://blog.csdn.net/qq_30572275/article/details/80343775

猜你喜欢

转载自blog.csdn.net/qq_39192827/article/details/86505964