并发编程 — 6.3 JUC — 阻塞同步机制AQS

AbstractQueuedSynchronizer(AQS),即队列同步器。它是构建锁或者其他同步组件的基础框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),它是JUC并发包中的核心基础组件。AQS的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态。

Node节点

AQS维护了一个静态内部类Node,它包含了以下属性:

  1. static final Node SHARED:一种标识,表示被标识的结点是共享式。
  2. static final Node EXCLUSIVE:一种标识,表示被标识的结点是独占式。
  3. volatile int waitStatus:当前结点的等待状态,它有1,0,-1,-2,-3五个取值。
    1. static final int CANCELLED =  1;  被中断或者获取同步状态超时会被置为该状态,且在该状态下的线程不再被阻塞。
    2. 0为初始化默认值。
    3. static final int SIGNAL    = -1;  线程如果释放了或者取消了同步状态,则会将对应的结点置为该状态,用于通知下一个节点,准备获取同步状态。
    4. static final int CONDITION = -2; 当前节点在Condition中的等待队列上,其他线程调用了singal方法后,该节点会从等待队列转移到AQS的同步队列上,等待获取同步锁。
    5. static final int PROPAGATE = -3; 与共享式获取同步状态有关,该状态标识的结点对应线程处于可运行的状态。
  4. volatile Node prev:前驱结点(给CLH同步队列使用)
  5. volatile Node next:后继结点(给CLH同步队列使用)
  6. volatile Thread thread:当前节点对应的线程
  7. Node nextWaiter:当前节点在Condition中等待队列上的下一个节点(给Condition等待队列使用)

上述属性中,注意其中a、b以及c中的状态信息都是static类型的,也就是说它们只是一些标识信息,是所有节点共用的。而其他属性则是每个节点自身的信息。

CLH同步队列

CLH(Craig, Landin, and Hagersten locks) 同步队列 是一个FIFO双向队列,其内部通过节点head和tail记录队首和队尾元素,队列元素的类型为Node。AQS依赖它来完成同步状态state的管理,CLH提供了一些重要的函数:

线程同步状态相关

  • tryAcquire(int arg):独占式获取同步状态,获取同步状态成功后,其他线程需要等待该线程释放同步状态才能获取同步状态
  • tryRelease(int arg):独占式释放同步状态;
  • tryAcquireShared(int arg):共享式获取同步状态,返回值大于等于0则表示获取成功,否则获取失败;
  • tryReleaseShared(int arg):共享式释放同步状态;
  • acquire(int arg):独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则,将会进入同步队列等待,该方法将会调用可重写的tryAcquire(int arg)方法;
  • acquireInterruptibly(int arg):与acquire(int arg)相同,但是该方法响应中断,当前线程为获取到同步状态而进入到同步队列中,如果当前线程被中断,则该方法会抛出InterruptedException异常并返回;
  • tryAcquireNanos(int arg,long nanos):超时获取同步状态,如果当前线程在nanos时间内没有获取到同步状态,那么将会返回false,已经获取则返回true;
  • acquireShared(int arg):共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式的主要区别是在同一时刻可以有多个线程获取到同步状态;
  • acquireSharedInterruptibly(int arg):共享式获取同步状态,响应中断;
  • tryAcquireSharedNanos(int arg, long nanosTimeout):共享式获取同步状态,增加超时限制;
  • release(int arg):独占式释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒;
  • releaseShared(int arg):共享式释放同步状态;

CLH状态相关

  • getState():返回同步状态的当前值;
  • setState(int newState):设置当前同步状态;
  • compareAndSetState(int expect, int update):使用CAS设置当前状态,该方法能够保证状态设置的原子性;
  • CLH队列入列就是tail指向新节点、新节点的prev指向当前最后的节点,当前最后一个节点的next指向当前节点。

同步状态的获取与释放

在AQS中维护着一个FIFO(先进先出)的同步队列CLH,当线程获取同步状态失败后,则会加入到这个CLH同步队列的对尾并一直保持着自旋。在CLH同步队列中的线程在自旋时会判断其前驱节点是否为首节点,如果为首节点则不断尝试获取同步状态,获取成功则退出CLH同步队列。当线程执行完逻辑后,会释放同步状态,释放后会唤醒其后继节点。

获取同步状态(入口:acquire相关方法)

释放同步状态(入口:release相关方法)

Condition等待队列

在经典的生产者-消费者模式中,可以使用Object.wait()和Object.notify()阻塞和唤醒线程,但是只能有一个等待队列。在AQS中也提供了类似的机制,但是可以更灵活的建立多个等待队列。Condition队列有以下特点:

  1. 提供了await()方法使一个线程陷入等待,并提供signal()方法来支持线程的唤醒。
  2. Condition和Wait/Notify通知机制类似,必须在Lock中使用,即在使用之前必须是已经获取到锁的状态(Wait/Notify必须在synchronized中使用)。
  3. 执行signal()方法后,等待队列的结点被转移至CLH同步队列中竞争锁。

Condition等待队列也使用了和CLH同步队列一样的结点,类似地使用firstWaiter与lastWaiter两个指针指向队列首尾,但是只使用了node的nextWaiter属性(而没有用next和prev)使其变成了一个单向链表(也是FIFO)。Condition等待队列提供了一些控制线程状态的重要函数:

  1. 线程等待阻塞
    1. await():让当前线程进入等待阻塞状态,使其转移到condition队列中,直到其他线程唤醒或者中断此线程。
    2. await(long time, TimeUnit unit):让当前线程进入等待阻塞状态,使其转移到condition队列中,直到其他线程唤醒、中断、超时。
    3. awaitNanos(long nanosTimeout):纳秒级别控制,让当前线程进入等待阻塞状态,使其转移到condition队列中,直到其他线程唤醒、中断、超时,并返回剩余时间。
    4. awaitUninterruptibly():让当前线程进入等待阻塞状态,且不会响应线程的中断。
    5. awaitUntil(Date deadline):类似于第二种情况。
  2. 线程唤醒
    1. signal():唤醒一个等待在Condition上的线程,使其转到同步队列中
    2. signalAll():唤醒所有等待在Condition上的线程,使它们转到同步队列中

LockSupport

可以发现,在同步队列和等待队列中对线程的很多操作都与LockSupport有关。LockSupport对外提供了一些静态方法用来操控当前线程:

  1. 线程阻塞相关
    1. park():阻塞当前线程,直到其他线程调用unpark(Thread)方法或者当前线程被中断
    2. parkNanos(long):阻塞当前线程,直到其他线程调用unpark(Thread)方法、当前线程被中断、超时(相对时间)
    3. parkUntil(long):阻塞当前线程,直到某个时间(绝对时间)
  2. 线程唤醒相关
    1. unpark(Thread):唤醒某个线程

而LockSupport的底层还是由Unsafe类来完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值