一.AQS
1.概述
AQS(AbstractQueuedSynchronizer),是抽象队列同步器,其实就是一个用来构建锁和同步器的框架。内部实现的关键是:先进先出的队列、state状态。
2.核心思想
AQS核心思想是:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,AQS使用一个voliate int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。
AQS定义了两种资源获取方式:独占(只有一个线程能访问执行)和共享(多个线程可同时访问执行)。
二.ReentrantLock和AQS的关系
在LOCK包中的相关锁(常用的有ReentrantLock、 ReadWriteLock)都是基于AQS来构建。
ReentrantLock内部包含了一个AQS对象,也就是AbstractQueuedSynchronizer类型的对象。这个AQS对象就是ReentrantLock可以实现加锁和释放锁的关键性的核心组件。
三、ReentrantLock(可重入锁)
1.概述
ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁,支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。ReentrantLock主要利用CAS+AQS队列来实现。它支持公平锁和非公平锁,两者的实现类似。
2.ReentrantLock获取锁定有四种方式
- lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
- tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false
- tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
- lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到获取锁定,或者当前线程被别的线程中断
示例
private Lock lock = new ReentrantLock();
public void test(){
lock.lock();
try{
doSomeThing();
}catch (Exception e){
// ignored
}finally {
lock.unlock();
}
}
3.ReentrantLock源码解析
(1)new ReentrantLock() 实例化
构造函数有两个,无参数的构造函数默认是非公平锁;有参数的构造函数,如果传入的参数是true,则是公平锁,传入false,则是非公平锁。
// 重入锁默认采用非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// true:公平锁 false:非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock的公平锁和非公平锁都继承Sync,Sync又继承AQS,因此可重入锁的底层采用的AQS来实现的。
(2)ReentrantLock调用Sync的lock方法并根据公平或非公平的lock逻辑执行
示意图:
源码:
public class ReentrantLock implements Lock, java.io.Serializable {
...
public void lock() {
// 分为公平锁的lock()实现和非公平锁的lock()实现
sync.lock();
}
// 公平锁
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
}
// 非公平锁
static final class NonfairSync extends Sync {
final void lock() {
// 【重点】使用CAS将AQS.state置为1,表示已抢占该锁,否则失败进入else
if (compareAndSetState(0, 1)) {
// 将AbstractOwnableSynchronizer.exclusiveOwnerThread置为当前线程
setExclusiveOwnerThread(Thread.currentThread());
} else {
// 走公平锁的流程
acquire(1);
}
}
}
...
}
非公平锁在抢占失败后,和公平锁一样,执行acquire(1)
public final void acquire(int arg) {
/**
* tryAcquire(1): 判断当前线程是否成功的抢占锁
* addWaiter(Node.EXCLUSIVE): 构建一个独占模式节点Node,并维护好该节点的前后指针Node
* acquireQueued(addWaiter(Node.EXCLUSIVE), 1):
*/
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
// 设置当前线程的中断标识
selfInterrupt();
}
}
(3)执行tryAcquire方法去尝试获取锁
- 公平锁
/**
* 进行抢锁操作,返回是否成功
* 抢占成功条件:
* case1> 没人抢占锁,线程A执行抢占锁操作,执行成功。
* case2> 有人已经抢占了这个锁,但是抢占这个锁的线程就是线程A自己,那么对自己重入加锁,执行成功。
*
* true:抢占到了锁 false:没有抢到锁
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
/** 如果c == 0,说明可以抢占锁 */
if (c == 0) {
/** 如果线程不需要排队 并且 抢占锁成功(即:如果state=0,则将该值修改为1,CAS操作成功)*/
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
// 设置抢到锁的线程为current
setExclusiveOwnerThread(current);
return true;
}
}
/** 如果c != 0,判断,是否是重入操作(即:锁本来就是被自己抢占的,支持多次抢占。) */
else if (current == getExclusiveOwnerThread()) {
// 相当于state+1
int nextc = c + acquires;
if (nextc < 0) {
throw new Error("Maximum lock count exceeded");
}
setState(nextc);
return true;
}
return false;
}
- 非公平锁
/**
* 进行抢锁操作,是否抢到非公平锁
*
* 处理内容:
* 1>如果抢到锁,返回true
* 1.1>如果当前线程第一次抢到锁:
* AQS.status由0变为1
* AQS.exclusiveOwnerThread=Thread.currentThread()
* 返回true
* 1.2>如果当前线程再次抢到锁(重入加锁):
* AQS.status++
* 返回true
* 2>如果没抢到锁,返回false
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
/**
* 获得当前独享线程,如果就是当前线程,那么执行重入操作
* 执行tryLock()时:
* 如果第二次进入,则nextc = 0 + 1 = 1
* 如果第三次进入,则nextc = 1 + 1 = 2
* 如果第四次进入,则nextc = 2 + 1 = 3
*/
int nextc = c + acquires;
// overflow 溢出
if (nextc < 0) {
throw new Error("Maximum lock count exceeded");
}
setState(nextc);
return true;
}
// nf-eg—2:线程B 返回false
return false;
}
(4)addWaiter方法
(5)acquireQueued方法
(6)lock.unlock() 解锁
4.ReentrantLock加锁和释放锁的底层原理
(1)初始状态:
AQS对象内部有一个核心的变量叫做state,是用voliate修饰的int类型,代表了加锁的状态,初始状态下,这个state的值是0,表示没有加锁。
另外,这个AQS内部还有一个关键变量,用来记录当前加锁的是哪个线程,初始化状态下,这个变量是null。
(2)有一个线程调用ReentrantLock的lock()方法尝试进行加锁
当线程1尝试加锁,如果当前state=0,直接通过CAS将state=1,设置成功后,设置当前加锁线程为当前线程(线程1),此时,线程1就获得了锁。
可重入锁:如果一个线程尝试加锁,发现state!=0,判断加锁线程是否为自己,如果为自己,就可以可重入加锁,将state+1即可。
(3)加锁失败情况
当线程2尝试加锁,发现state!=0,并且当前加锁线程不是自己,则加锁失败,将自己放入到等待队列中。
(4)释放锁
当线程1全部执行完后,就会释放锁。释放锁就是将AQS内的state值递减1,直到state=0,释放锁,加锁线程设置成null。
此时,等待队列中的线程被唤醒,将state设置成1,加锁线程设置成自己,并从等待队列中出队列。