一、AQSSynchronizer基本介绍AbstractQueuedSynchronizer(简称AQS)是构建其他同步组件的基础。它使用一个int变量来表示同步状态state,通过内置的FIFO队列来完成线程的排队工作。2、如何使用AQS搭建同步组件?同步器的设计基于模板模式,用户继承同步器并重写指定的方法。同步器可重写的方法如下:方法名称描述booleantryAcquire(intarg)尝试独占获取同步状态,实现该方法需要查询当前状态判断同步状态是否符合预期,然后进行CAS设置同步状态booleantryRelease(intarg)尝试独占释放同步状态,等待获取同步状态的线程将有机会获取同步状态。inttryAcquireShared(intarg)尝试共享同步状态,返回大于等于0的值,表示获取成功,否则获取失败。booleantryReleaseShared(intarg)尝试以共享方式释放同步状态模板方法如下:方法名称说明voidacquire(intarg)独占获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则进入同步队列等待,该方法会调用重写的tryAcquire方法voidacquireInterruptibly(intarg)与acquire相同,但该方法响应中断。如果当前线程没有获取同步状态就进入同步队列,如果当前线程被中断,该方法会抛出InterruptedException异常booleantryAcquireNanos(intarg,longnanos)在acquireInterruptibly的基础上增加了超时限制,如果当前线程threadistimeout如果在时间内没有获取到同步状态,则返回false,否则返回truevoidacquireShared(intarg)以共享的方式获取同步状态。如果当前线程没有获得同步状态,就会进入同步队列等待。与exclusive方法的主要区别是同一个多个线程可以随时获取同步状态在acquireSharedInterruptiblybooleanrelease(intarg)exclusive释放同步状态的基础上,该方法在释放同步状态后唤醒同步队列第一个节点的线程booleanreleaseShared(intarg)shared释放同步状态CollectiongetQueuedThreads()获取同步队列上等待的线程集合3、AQS的实现分析从实现的角度分析同步器是如何完成线程同步的。同步队列独占同步状态获取与释放共享同步状态获取与释放超时获取同步状态3.1同步队列AQS内部是基于同步队列(一种双向队列)来完成对同步状态的管理。当当前线程获取同步状态失败时,会将当前线程和等待状态信息构造成一个Node节点加入到同步队列中,同时挂起当前线程。当同步状态被释放时,第一个节点中的线程将被唤醒。让它再次获得同步状态。Node类结构如下:属性类型和名称描述intwaitStatus等待状态。1.CANCELLED,值为1,因为在同步队列中等待的线程超时或被中断,需要从同步队列中取消等待,进入该状态时节点不会发生变化。2.SIGNAL,值为-1,后继节点的线程处于等待状态,如果当前节点的线程释放同步状态或者被取消,会通知后继节点,让后继节点的线程后继节点可以运行。3.CONDITION,值为-2,节点在等待队列中,节点线程正在等待Condition。当其他线程调用Condition上的signal()方法时,该节点将从等待队列转移到同步队列,加入到同步状态的获取中。4.PROPAGATE,值为-3,表示下一次共享同步状态获取将无条件传播。5.INITIAL,值为0,初始状态为Nodeprev。当节点加入同步队列后,设置为Nodenext。后继节点。如果当前节点是共享的,则该字段是一个SHARED常量。也就是说,节点类型(独占或共享)和等待队列中的后继节点共享同一个字段Thread。thread对应的threadNode节点是同步队列的基础。获取不到同步状态的线程会成为Node节点,加入队列的末尾,头节点是一个有同步状态的节点(如果是第一次初始化,头节点会是一个空Node,这里只是请记住,头节点后面的节点都是等待获取同步状态的节点)。同步队列的基本结构如下:3.2同步状态的独占获取和释放同步状态的获取:同步状态是通过调用同步器的acquire(intarg)方法获取的,同步器不能响应中断。以下代码完成同步状态获取、节点构建、加入同步队列等相关工作。publicfinalvoidacquire(intarg){if(!tryAcquire(arg)&&acquireQueued(addWaiter(Node.EXCLUSIVE),arg))selfInterrupt();}过程如下:调用自定义同步器,该方法确保同步状态的线程安全获取。如果获取成功,则将独占线程设置为当前线程。如果获取失败,则构造一个节点,通过addWaiter(Nodenode)方法将该节点添加到同步队列的尾部。然后调用acquireQueued(Nodenode,intarg)方法让节点再次尝试获取同步状态。如果获取不到同步状态,则会调用LockSupport.park()方法挂起当前线程,挂起线程的唤醒主要依赖于前驱节点的输出。队列或线程中断实现。释放同步状态:当前线程获得同步状态并执行相应逻辑后,需要释放同步状态,以便后续节点可以继续获得同步状态。可以通过调用同步器的release(intarg)方法来释放同步状态,同步器释放同步状态后会唤醒其后继节点。publicfinalbooleanrelease(intarg){if(tryRelease(arg)){Nodeh=head;如果(h!=null&&h.waitStatus!=0)unparkSuccessor(h);返回真;}returnfalse;}总结:在获取同步状态时,同步器会维护一个同步队列,获取状态失败的线程会被添加到队列尾部。这里我们会先判断前驱节点是否为头节点,如果是,则尝试获取同步状态,如果获取成功,则直接将该节点设置为头节点,如果获取失败,则挂起当前线程。释放同步状态时,同步器调用release(intarg)方法释放同步状态,唤醒头节点的后继节点。3.3同步状态的共享获取和释放通过调用同步器的acquireShared(intarg)方法,可以共享获取同步状态。publicfinalvoidacquireShared(intarg){if(tryAcquireShared(arg)<0)doAcquireShared(arg);}privatevoiddoAcquireShared(intarg){finalNodenode=addWaiter(Node.SHARED);布尔失败=真;尝试{布尔中断=false;for(;;){最终节点p=node.predecessor();如果(p==head){intr=tryAcquireShared(arg);如果(r>=0){setHeadAndPropagate(节点,r);p.next=null;如果(中断)selfInterrupt();失败=假;返回;}}if(shouldParkAfterFailedAcquire(p,node)&&parkAndCheckInterrupt())中断=true;}}最后{如果(失败){cancelAcquire(节点);}}}在acquireShared(intarg)方法中,同步器调用tryAcquireShared(intarg)方法尝试获取同步状态,该方法返回一个int值,如果大于等于0,则表示可以获取同步状态,否则无法获取同步状态。doAcquireShared(intarg)方法的自旋过程中,如果当前节点的前驱是头节点,尝试获取同步状态,如果返回值大于等于0,则表示同步状态为成功获取并退出自旋过程。和独占类型一样,共享类型也需要释放同步状态,可以通过调用releaseShared(intarg)方法来释放。它和exclusive方法的主要区别是tryReealseShared(intarg)方法必须保证同步状态的安全释放,一般通过loop+CAS来保证。publicfinalbooleanreleaseShared(intarg){if(tryReleaseShared(arg)){doReleaseShared();返回真;}returnfalse;}3.4超时获取同步状态可以通过调用同步器的doAcquireNanos(intarg,longnanosTimeout)方法超时获取同步状态,获取指定时间段内的同步状态,如果获取到,它返回真,否则返回假。随时间获取同步状态的过程是响应中断获取同步状态过程的升级版。doAcquireNanos方法支持响应中断,增加了超时获取的特性。该方法提供了synchronized关键字所没有的特性。独占超时获取同步状态和独占获取同步状态在流程上非常相似,主要区别在于未获取到同步状态时的处理逻辑。acquire(intarg)在没有获取到同步状态时,会让当前线程保持等待状态,doAcquireNanos()方法会让当前线程等待nanosTimeout纳秒,如果当前线程在nanosTimeout内没有获取到同步状态nanoseconds,会直接返回false。4.总结自定义同步组件可以通过继承AQS并实现其抽象方法来管理同步状态。同步器可以同时支持同步状态的独占获取和同步状态的共享获取,实现不同类型的同步组件(如ReentrantLock可重入锁、ReentrantReadWriteLock读写锁和CountDownLatchlatch)
