推荐你阅读
互联网大厂万字专题总结
Redis总结
JUC总结
操作系统总结
JVM总结
Mysql总结
互联网大厂常考知识点
什么是系统调用
CPU底层锁指令有哪些
AQS与ReentrantLock原理
旁路策略缓存一致性
Java通配符看这一篇就够
AQS 原理
AQS 是一个 java 层面实现的同步框架,借助 AQS 可以实现很多锁机制与同步器等,AQS 底层借助于 unsafe 提供的 cas 接口和 park & unpark 接口。
下面我们基于 AQS 实现一个自定义阻塞式锁,实现 Lock 接口规定的阻塞式获取锁、可打断阻塞式获取锁、非阻塞式获取锁、可打断可超时阻塞式获取锁、释放锁方法,并详细介绍 AQS 在整个过程中是如何工作的。
如下所示,可以看到定义了一个 MySync 成员内部类实现了 AQS 抽象父类并重写了 tryAcquire、tryRelease 和 isHeldExclusively 方法,AQSLock 类实现了 Lock 接口并持有一个 MySync 对象,实现了 Lock 接口的几种方法。
package org.howard1209a.juc;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
public class AQSLock implements Lock {
private MySync sync = new MySync();
private class MySync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
}
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return null;
}
}
lock 方法
调用了 AQS 的 acquire 方法
首先调了我们自己实现的 tryAcquire 方法,该方法是尝试获取锁,可以看到尝试 cas state,保证了只有一个线程可以获取到锁,该线程会将 exclusiveOwnerThread 设置为自己
返回 acquire 方法,获取到锁的线程可以直接返回,没获取到锁的线程会进入 addWaiter 方法,该方法是创建一个当前线程对应的节点,并让节点进队。tail 是 AQS 的一个属性,最开始是 null,因此直接进入 enq 方法
tail 为 null,因此会创建一个新的 node 并 cas 这个 tail 属性,保证多线程唯一创建 tail,这里是一个循环,当 tail 有节点时就会把 node 的前置节点设置为 tail 节点,然后** cas tail 为 node**,该操作只有一个线程会成功,其他线程失败后会再重试,最后所有的线程都会成功将 node 节点加入到队列中。简单来说就是 enq 方法利用 cas 操作完成了多线程并发入队。
后续 tail 不为 null 之后,addWaiter 方法会先尝试一次进队,如果失败再进 enq 方法。
addWaiter 方法构造节点并进队之后,就会进入 acquireQueued 方法,node.predecessor 方法会获取 node 节点的前一个节点,可以看到如果是队列中的第一个节点(除了 head 节点之外的),会尝试再次 tryAcquire 获取一次锁,如果获取成功会直接返回。如果没有获取到锁或者不是第一个节点,就会调用 shouldParkAfterFailedAcquire 方法判断是否要阻塞,这个方法我们不关注,不妨认为永远返回 true,这样就会进入 parkAndCheckInterrupt 方法
parkAndCheckInterrupt 方法比较简单,利用 LockSupport.park 阻塞当前线程,这里恢复调用的方式有两种,一个是当其他线程释放锁的时候会 unpark,另一个是 interrupt 打断。
返回 acquireQueued 方法,一旦阻塞恢复调度之后,会循环执行之前的过程,只有第一个节点会尝试再次获取锁,注意虽然是第一个节点,但这并不意味着是公平锁,因为某个其他线程释放锁后,有可能有新的线程调用 tryAcquire 方法抢先获取到锁。
接下里我们看 unlock 释放锁接口,调用了 AQS 类的 release 方法
首先调 tryRelease 方法,这是我们自定义的方法
首先将 exclusiveOwnerThread 设置为 null,然后修改状态为 0,state 是 AQS 类的一个 int 属性,因此写入是原子的。
返回 release 方法,在释放锁后会判断如果 head 不为 null 时,会调用 unparkSuccessor 方法,可以看到获取了 head 的 next 节点(也就是第一个节点),然后** unpark 唤醒它**。
lockInterruptibly 方法
阻塞式获取锁但可打断,可以看到 acquireInterruptibly 会首先判断一下当前线程的打断标志位,如果打断标志位为 true 就直接抛异常,否则尝试获取锁,获取成功直接返回,失败进 doAcquireInterruptibly 方法
doAcquireInterruptibly 方法就和之前基本一致了,不同的是多了一个抛异常的动作
tryLock 方法
尝试一次获取锁没啥可说的,主要讲一下 tryLock(long time, TimeUnit unit) 方法,阻塞式获取锁,可打断可超时退出。
仍然先判断一下打断标志位
和之前的区别主要在于,如果 park 被打断了会直接抛出异常,阻塞操作利用 LockSupport.parkNanos 阻塞特定时间,超时恢复调度后下一次循环就会返回。
ReentrantLock 原理
上述实现的 AQSLock 其实就是参照了 ReentrantLock 的实现,我们简单讲一下 ReentrantLock 阻塞式获取锁和释放锁的过程
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
your code...
} finally {
lock.unlock();
}
lock
ReentrantLock 有两个 AQS 实现类,分别是 FairSync 和 NonfairSync,默认使用的 NonfairSync 非公平实现。
可以看到也是先 cas state,成功后设置 exclusiveOwnerThread 为当前线程,失败就进 AQS 的 acquire 方法
acquire 方法和之前一样
看看 NonfairSync 对 tryAcquire 的实现,如果 state 为 0 还是之前的 cas 逻辑,如果 state 不为 0 说明锁当前被一个线程占有,因为 ReentrantLock 是可重入的,所以如果锁属于当前线程会将本次 acquire 的值叠加在 state 上然后返回获取锁成功。
后面就和之前一样了
unlock 方法
unlock 方法和我们自己实现的 AQSLock 类的区别在于 tryRelease 方法,由于是可重入锁,所以要用 state 减去 releases 值,如果为 0 才释放锁,否则只是更新 state 的值