Java多线程之Condition

90 阅读5分钟

Condition是Lock接口中实现像Object类中的wait()和notify()等的一个方式,前面我们只说到了如何进行lock()和unlock()以及里边的原理,那我们来说下使用AQS的基础下,如何实现阻塞释放锁。

Condition接口

任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包括wait()、wait(long timeout)、notify()以及notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式。Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式。

对比项Object Monitor MethodsCondition
前置条件获取对象的锁调用Lock.lock()获取锁调用Lock.newCondition()获取Condition对象
调用方式直接调用 如:object.wait()直接调用 如:condition.await()
等待队列个数一个多个
当前线程释放锁并进入等待状态到将来的某个时间不支持支持
当前线程释放锁并进入等待状态,在等待状态中不响应中断不支持支持

Condition是依赖Lock对象的,一般都会将Condition作为成员变量。

Condition的实现分析

ConditionObject是同步器AQS的内部类,每个Condition对象都包含着一个队列(等待队列),该队列是Condition对象实现等待/通知功能的关键。

等待队列

  • 等待队列是一个FIFO队列,在队列中的每个节点都包含一个线程引用,该线程就是在Condition对象上等待的线程,如果一个线程调用了Condition.await(),那么该线程将会释放锁,构造成节点加入等待队列并进入等待状态。
  • 等待队列中的节点和同步队列的节点类型都是AQS的静态内部类AbstractQueuedSynchroniezer.Node。
  • 一个Condition包含一个等待队列,Condition拥有首节点和尾节点,当线程调用Condition.await()方法,将会以当前线程构成节点,并将节点从尾部加入等待队列
    • 上述更新过程无需CAS保证,因为调用await()的时候一定是获取了锁
  • Lock可以拥有一个同步队列和多个等待队列
  • Condition的实现是同步器的内部类,因此每个Condition实例都能访问同步器提供的方法

等待

  • 调用Condition的await()方法,会使得当前线程进入等待队列并释放锁,同时线程状态变为等待状态。当从await()方法返回时,当前线程一定获取了Condition相关联的锁
  • 调用await()方法,相当于把同步队列的头节点插入到等待队列的尾部
public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
        	//将当前线程构成一个新的节点,加入等待队列
            Node node = addConditionWaiter();
        	//释放同步状态,也就是释放锁
            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);
        }

当等待队列中的节点被唤醒,则唤醒节点的线程开始尝试获取同步状态。如果不是通过其他线程调用Condition.signal()方法唤醒,而是对等待线程进行中断,则会抛出InterruptedException。

通知

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

 public final void signal() {
            if (!isHeldExclusively())//表示是否被当前状态占用
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }

调用该方法的前置条件是当前线程必须获取了锁,接着在获取等待队列的第一个节点,再将其移动到同步队列并使用LockSupport唤醒节点中的线程。

  • 通过调用AQS的enq(Node node)方法,等待队列中的头节点线程安全地移动到同步队列。当节点移动到同步队列中,当前线程再使用LockSupport唤醒该节点的线程。
  • 被唤醒的线程将从await()方法中的while循环中退出,进而调用同步器acquireQueued()方法加入到获取同步状态的竞争中
  • 成功获取同步状态以后,被唤醒的线程将从先前调用的await()方法返回,此时该线程已经成功地获取了锁。

小结

一般在获取到锁的时候,才会将其加入到等待队列(队尾)中,也就是进行阻塞,当调用其中的一个Condition的signal方法的时候,会唤醒当前调用signal()方法的Condition对象(等待队列)的队头节点,并将其加入到同步队列的队尾,使其能过够进行自旋判断自己是否获取同步状态

一个打印100次ABCABCABC的例子

ReentrantLock lock = new ReentrantLock();
Condition conditionA = lock.newCondition();
Condition conditionB = lock.newCondition();
Condition conditionC = lock.newCondition();
Thread A =new Thread(){
   @Override
   public void run() {
       try {
           lock.lock();
           for (int i = 0; i < 100; i++) {
               while (state % 3 != 0) {
                   conditionA.await();
               }
               state++;
               System.out.print("A");
               //当A执行完以后 要去唤醒B线程
               conditionB.signal();
           }
       }catch (InterruptedException e){
           e.printStackTrace();
       }finally {
           lock.unlock();
       }
   }
};
Thread B =new Thread(){
   @Override
   public void run() {
       try {
           lock.lock();
           for (int i = 0; i < 100; i++) {
               while (state % 3 != 1) {
                   conditionB.await();
               }
               state++;
               System.out.print("B");
               //当B执行完以后 要去唤醒C线程
               conditionC.signal();
           }
       }catch (InterruptedException e){
           e.printStackTrace();
       }finally {
           lock.unlock();
       }
   }
};
Thread C =new Thread(){
   @Override
   public void run() {
       try {
           lock.lock();
           for (int i = 0; i < 100; i++) {
               while (state % 3 != 2) {
                   conditionC.await();
               }
               state++;
               System.out.print("C");
               //当C执行完以后 要去唤醒A线程
               conditionA.signal();
           }
       }catch (InterruptedException e){
           e.printStackTrace();
       }finally {
           lock.unlock();
       }
   }
};
A.start();
C.start();
B.start();