是什么

前边说了并发基础必学的《内存模型》,也提到了synchronized这个重量级锁,虽然在1.6后,进行大量的锁优化策略,但是与Lock相比synchronized还是存在一些缺陷的:虽然synchronized提供了便捷性的隐式获取锁释放锁机制(基于JVM机制),但是它却缺少了获取锁与释放锁的可操作性,可中断、超时获取锁,且它为独占式在高并发场景下性能大打折扣。
因为后边会用到很多并发编程的利器CountDownLatch、CyclicBarrier、Sempahore、ReentrantLock、ReentrantReadWriteLock,他们是通过AbstractQueuedSynchronizer抽象类(下面简写AQS)来实现的,那么什么是AQS:它是一个抽象队列同步器,用来构建锁或者其他同步组件。

如何用

AQS的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态。
AQS使用一个int类型的成员变量state来表示同步状态,当state>0时表示已经获取了锁,当state = 0时表示释放了锁。它提供了三个方法来对同步状态state进行操作,当然AQS可以确保对state的操作是安全的,它是原子式的管理同步状态。

  • getState():返回同步状态的当前值;
  • setState(int newState):设置当前同步状态;
  • compareAndSetState(int expect, int update):使用CAS设置当前状态,该方法能够保证状态设置的原子性;

AQS通过内置的FIFO同步队列来完成资源获取线程的排队工作,如果当前线程获取同步状态失败(锁)时,AQS则会将当前线程以及等待状态等信息构造成一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,则会把节点中的线程唤醒,使其再次尝试获取同步状态。

同步队列

在同步队列中,一个节点表示一个线程,它保存着线程的引用(thread)、状态(waitStatus)、前驱节点(prev)、后继节点(next),其定义源码如下:

  1. static final class Node {
  2. /** 共享 */
  3. static final Node SHARED = new Node();
  4. /** 独占 */
  5. static final Node EXCLUSIVE = null;
  6. /**
  7. * 因为超时或者中断,节点会被设置为取消状态,被取消的节点时不会参与到竞争中的,他会一直保持取消状态不会转变为其他状态;
  8. */
  9. static final int CANCELLED = 1;
  10. /**
  11. * 后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消,将会通知后继节点,使后继节点的线程得以运行
  12. */
  13. static final int SIGNAL = -1;
  14. /**
  15. * 节点在等待队列中,节点线程等待在Condition上,当其他线程对Condition调用了signal()后,改节点将会从等待队列中转移到同步队列中,加入到同步状态的获取中
  16. */
  17. static final int CONDITION = -2;
  18. /**
  19. * 表示下一次共享式同步状态获取将会无条件地传播下去
  20. */
  21. static final int PROPAGATE = -3;
  22. /** 等待状态 */
  23. volatile int waitStatus;
  24. /** 前驱节点 */
  25. volatile Node prev;
  26. /** 后继节点 */
  27. volatile Node next;
  28. /** 获取同步状态的线程 */
  29. volatile Thread thread;
  30. Node nextWaiter;
  31. Node() {
  32. }
  33. Node(Thread thread, Node mode) {
  34. this.nextWaiter = mode;
  35. this.thread = thread;
  36. }
  37. Node(Thread thread, int waitStatus) {
  38. this.waitStatus = waitStatus;
  39. this.thread = thread;
  40. }
  41. final boolean isShared() {
  42. return nextWaiter == SHARED;
  43. }
  44. final Node predecessor() throws NullPointerException {
  45. Node p = prev;
  46. if (p == null) {
  47. throw new NullPointerException();
  48. }
  49. else return p;
  50. }
  51. }

入队:tail指向新节点、新节点的prev指向当前最后的节点,当前最后一个节点的next指向新节点。可以查看addWaiter(Node node)方法。
出队:首节点的线程释放同步状态后,将会唤醒它的后继节点(next),而后继节点将会在获取同步状态成功时将自己设置为首节点,这个过程非常简单,head执行该节点并断开原首节点的next和当前节点的prev即可,注意在这个过程是不需要使用CAS来保证的,因为只有一个线程能够成功获取到同步状态。

同步状态

AQS的设计模式采用的模板方法模式,子类通过继承的方式,实现它的抽象方法来管理同步状态,对于子类而言它并没有太多的活要做,AQS提供了大量的模板方法来实现同步,主要是分为三类:独占式获取和释放同步状态、共享式获取和释放同步状态、查询同步队列中的等待线程情况。自定义子类使用AQS提供的模板方法就可以实现自己的同步语义。
独占式

  • tryAcquire(int arg):独占式获取同步状态,获取同步状态成功后,其他线程需要等待该线程释放同步状态才能获取同步状态。
  • acquire(int arg):独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则,将会进入同步队列等待,该方法将会调用可重写的tryAcquire(int arg)方法。
  • acquireQueued:当前线程会根据公平性原则来进行阻塞等待(自旋),直到获取锁为止;并且返回当前线程在等待过程中有没有中断过。

共享式:

  • tryAcquireShared(int arg):共享式获取同步状态,返回值大于等于0则表示获取成功,否则获取失败;
  • tryAcquireNanos(int arg,long nanos):超时获取同步状态,如果当前线程在nanos时间内没有获取到同步状态,那么将会返回false,已经获取则返回true;
  • acquireShared(int arg):共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式的主要区别是在同一时刻可以有多个线程获取到同步状态;
  • acquireSharedInterruptibly(int arg):共享式获取同步状态,响应中断;
  • tryAcquireSharedNanos(int arg, long nanosTimeout):共享式获取同步状态,增加超时限制;