并发编程 系列 AQS

开启 并发编程 新篇章

Posted by lichao modified on May 16, 2020

ReentrantLock 意思为可重入锁,指的是一个线程能够对一个临界资源重复加锁。为了帮助大家更好地理解ReentrantLock的特性,我们先将ReentrantLock跟常用的Synchronized进行比较,其特性如下(蓝色部分为本篇文章主要剖析的点):

jvm

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// **************************Synchronized的使用方式**************************
// 1.用于代码块
synchronized (this) {}
// 2.用于对象
synchronized (object) {}
// 3.用于方法
public synchronized void test () {}
// 4.可重入
for (int i = 0; i < 100; i++) {
 synchronized (this) {}
}
// **************************ReentrantLock的使用方式**************************
public void test () throw Exception {
 // 1.初始化选择公平锁、非公平锁
 ReentrantLock lock = new ReentrantLock(true);
 // 2.可用于代码块
 lock.lock();
 try {
  try {
   // 3.支持多种加锁方式,比较灵活; 具有可重入特性
   if(lock.tryLock(100, TimeUnit.MILLISECONDS)){ }
  } finally {
   // 4.手动释放锁
   lock.unlock()
  }
 } finally {
  lock.unlock();
 }
}

ReentrantLock 与 AQS 的关联

ReentrantLock支持公平锁和非公平锁,并且ReentrantLock的底层就是由AQS来实现的。那么ReentrantLock是如何通过公平锁和非公平锁与AQS关联起来呢? 我们着重从这两者的加锁过程来理解一下它们与AQS之间的关系(加锁过程中与AQS的关联比较明显,解锁流程后续会介绍)。 非公平锁源码中的加锁流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// java.util.concurrent.locks.ReentrantLock#NonfairSync

// 非公平锁
static final class NonfairSync extends Sync {
 ...
 final void lock() {
  if (compareAndSetState(0, 1))
   setExclusiveOwnerThread(Thread.currentThread());
  else
   acquire(1);
  }
  ...
}

这块代码的含义为:

  • 若通过 CAS 设置变量State(同步状态)成功,也就是获取锁成功,则将当前线程设置为独占线程。
  • 若通过 CAS 设置变量State(同步状态)失败,也就是获取锁失败,则进入 Acquire 方法进行后续处理。

第一步很好理解,但第二步获取锁失败后,后续的处理策略是怎么样的呢?这块可能会有以下思考:

  • 某个线程获取锁失败的后续流程是什么呢?有以下两种可能:
    1. 将当前线程获锁结果设置为失败,获取锁流程结束。这种设计会极大降低系统的并发度,并不满足我们实际的需求。所以就需要下面这种流程,也就是AQS框架的处理流程。
    2. 存在某种排队等候机制,线程继续等待,仍然保留获取锁的可能,获取锁流程仍在继续。
  • 对于问题1的第二种情况,既然说到了排队等候机制,那么就一定会有某种队列形成,这样的队列是什么数据结构呢?
  • 处于排队等候机制中的线程,什么时候可以有机会获取锁呢?
  • 如果处于排队等候机制中的线程一直无法获取锁,还是需要一直等待吗,还是有别的策略来解决这一问题?

带着非公平锁的这些问题,再看下公平锁源码中获锁的方式:

1
2
3
4
5
6
7
8
9
10
// java.util.concurrent.locks.ReentrantLock#FairSync

static final class FairSync extends Sync {
  ...  
 final void lock() {
  acquire(1);
 }
  ...
}

看到这块代码,我们可能会存在这种疑问:Lock函数通过Acquire方法进行加锁,但是具体是如何加锁的呢?

结合公平锁和非公平锁的加锁流程,虽然流程上有一定的不同,但是都调用了Acquire方法,而Acquire方法是FairSync和UnfairSync的父类AQS中的核心方法。

对于上边提到的问题,其实在ReentrantLock类源码中都无法解答,而这些问题的答案,都是位于Acquire方法所在的类AbstractQueuedSynchronizer中,也就是本文的核心——AQS