浅谈JAVA中AbstractQueuedSynchronizer实现

AbstractQueuedSynchronizer(AQS)提供了实现锁和同步的一套基本框架。子类只需要通过简单的状态维护,即可实现lock or unlock。

下面,我们通过两个常用的需求,来看看如何通过AQS来实现。

案列一: CountDownLatch

  1. 主线程设置资源的数量, 比如资源数目是:7
  2. 多个子线程执行,执行完后,每个线程消耗资源:1
  3. 主线程等待,直到资源为0后, 才继续执行后面代码

案列二: ReentrantLock

  1. 对某个方法,我们要求必须同步
  2. 在线程一执行该方法时,其它线程执行该方法必须等待
  3. 线程一可以多次执行该方法

AQS设计

通过上面两个案列,我们可以提取出相应的关键点:

  1. 锁定资源 首先,该资源在多个线程中都需要立即看到最新的值,所以必须是atomic的或者volatile。其次该资源面临多个线程都可能去获取或者释放的问题,所以必须是原子的,常用的方法是atomic, VarHandle(java 9), Unsafe提供的compareAndSet来实现。

  2. 等待队列 在案列二中,由于同个线程在被自己获取资源的情况下,可以多次获取到该资源。所以,我们需要记录当前资源被哪个线程使用;其它线程尝试获取该资源,但是资源不可用的情况下,该线程需要进入等待队列,等待资源释放。(注:在这里资源可以理解为锁)

然后,我们来看看如何解决案列一和案列二。

案列一

  1. 我们要先设置资源数量setState(int state), 然后每个线程运行完成后,释放一个资源:tryReleaseShared(int releases)。

  2. 主线程tryAcquireShared,如果获取到state为0,那么以为其它线程执行完成,主线程获取资源成功;否则进入等待队列直到资源被释放唤醒。

案列二

  1. 线程一请求方法某资源, 如果当前state为0,则设置state为1,设置当前线程为互斥线程;或者,当前线程为互斥线程,则state数目加1。线程一或者资源成功。
  2. 线程二请求该资源,由于当前资源state不为0,获取资源失败,则线程二进行等待队列
  3. 线程一使用完成后,释放资源,如果state为0,则唤醒等待线程。

上述的两个案列我们都可以通过对state的值设定,来获取或者释放资源;使用队列来维护等待线程或者唤醒线程。AQS就是针对这个模型的具体实现。本文对模型进行了简化,AQS实现考虑的场景更过,更丰富,但是核心就是本文讲述的对状态和队列的维护。

你可能感兴趣的:(浅谈JAVA中AbstractQueuedSynchronizer实现)