队列同步器(AQS)详解-源码分析续——Condition接口

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qiuwenjie123/article/details/80055115

简介:

 

      我们知道,任何对象都可以作为监视器,而监视器都有wait()方法和notify()、notifyAll()等方法,而Condition接口也有类似的方法,如await()、signal()和signalAll()方法,他们的作用和监视器大同小异。在AQS中,有一个内部类ConditionObject,他就实现了Conditon接口。

Condition接口与监视器的区别:

      既然他们功能相似,那么区别是什么?以下就是他们之间的区别

可以看出,condition的功能比监视器要更完善,尤其是可以有多个等待队列(构造多个Condition对象)。

同时,Condition是与lock接口挂钩的,他依附于lock接口,必须通过lock接口获取到Condition对象。

简单的使用例子:

                Lock lock =new ReentrantLock();
		Condition condition=lock.newCondition();
		
		public void conditionWait() throws InterruptedException{
			lock.lock();
			try{
				condition.await();         //等待signal才从线程返回
			}finally {
				lock.unlock();
			}
		}
		
		public void conditionSignal() throws InterruptedException{
			lock.lock();
			try{
				condition.signal();        
			}finally{
				lock.unlock();
			}
			
		}

 

 

 

Condition的实现分析:

(1)等待队列

与AQS类似,一个Condition对象就维护了一个等待队列,Condition对象拥有首节点和尾节点,当前的线程调用了Condition.await()方法后,将会以当前的线程构造节点,然后将尾节点加入到等待队列。

 

private transient Node firstWaiter;
private transient Node lastWaiter;

  “Node即AQS中同步队列节点”

加入尾节点的操作不需要CAS保证,因为调用await()方法的线程必定是获取了锁的线程,也就是说锁保证了线程的安全性。

这么一说就可以看出锁(同步器)和监视器的区别:

      一个监视器对象拥有一个同步队列和一个等待队列;

      一个锁(同步器)拥有一个同步队列和多个等待队列;

(2)等待——await

我们来看一下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);
        }

调用该方法的线程之前已经成功的获取了锁,也就是AQS同步队列中的首节点,该方法将当前线程加入到等待队列中,然后释放同步状态,同时唤醒同步队列的后继节点。

至于为什么线程可以阻塞在这个方法,因为调用了fullyRelease方法之后,该线程就不在同步队列之中了,所以isOnSyncQueue方法返回false,使其在while循环体内死循环,然后通过工具类LockSupport的park方法阻塞线程

同步队列的首节点并不会直接的加入等待队列中,而是通过addConditonWaiter()方法把当前线程构造成一个新的节点并将其加入等待队列中。

 

当其他线程调用了signal方法之后,此线程会从while循环中退出,继续往下执行

那我们再看看方法acquireQueue()

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

这个方法之前在AQS中也有说到,那么在这里调用的意义就是:

      既然线程已经从等待队列之中返回,重新加入了同步队列之中,那么线程在这个方法的for死循环体内就又加入到获取同步状态的竞争之中。

详见:队列同步器(AQS)源码分析

(3)通知——signal

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

public final void signal() {
            if (!isHeldExclusively())                  //假如线程没有获取到锁就调用signal方法,则抛出异常    
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);                        //通知等待队列的首节点
        }

 

 

 

被唤醒后的线程,将从await()方法中的while循环中退出(isOnSycQueue方法返回true,节点已在同步队列之中)

展开阅读全文

没有更多推荐了,返回首页