承接上文,了解了AQS,现就来看看ReentrantLock 加解锁的逻辑。(不了解AQS的点击这里 -> AQS简述)
ReentrantLock加锁逻辑
这里以非公平锁为例来说明,这里画了个简化的流程图,先有个印象
ReentrantLock这个类,并没有直接继承AQS,而在ReentrantLock有一个内部类,sync
,它继承了AQS。
ReentrantLock lock = new ReentrantLock();
// 当执行这段代码时,就会创建一个AQS 对象,其status是0,具体原码如下
public ReentrantLock() {
sync = new NonfairSync(); // 默认是非公平锁
}
lock.lock(); // 加锁
执行这行代码时,会涉及到 自旋、 CAS、 入队、 阻塞,下面照着原始说逻辑,具体先不展开。
// 非公平锁实现
final void lock() {
if (compareAndSetState(0, 1)) // 成功将state 由0改为1的线程,直接就可以拿到锁
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 没拿到锁,做特殊处理
}
// 获取独占锁
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){
selfInterrupt();
}
}
acquire()
是获取独占锁的核心方法,咱先把整体逻辑讲完,然后再对原码进行逐行分析。
tryAcquire(arg)
尝试获取锁,成功返回true,否则返回false
addWaiter(Node.EXCLUSIVE)
自旋的方式入队,直到成功入队为止,否则重试
acquireQueued(final Node node, int arg)
再次尝试获取锁,若成功则返回,失败了就阻塞线程。其中阻塞线程是调用了 LockSupport.park()
方法
ReentrantLock解锁逻辑
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease(arg)
修改 state 属性,state = 0 时,即解锁成功
unparkSuccessor(h)
唤醒头节点,使其去抢锁,其中解除阻塞,是调用了LockSupport.unpark()
方法
用前文示例代码,来个第一阶段的总结
lock.lock(); // 假设10个线程同时走到这行,只可能有一个线程拿到锁,继续往下走,其他线程阻塞,停在这一行。
for(int j = 0; j < 1000; j++){
total++;
}
lock.unlock(); // 执有锁的线程释放了锁,阻塞的线程有机会抢锁,
// 抢锁成功的可以往下走,其余的继续阻塞。
在加锁解锁过程中,用了CAS
,用了自旋
,简要说下它俩啥意思,然后再开始讲原码。
CAS 可以保证,不论并发有多高,只可能有一个线程执行成功。
比如说,现在账户里有10块,现在要给账户上加5块钱,三个线程同时执行这个操作。
CAS要求传两个值过去 原来的值10,改动后的值15。底层执行的时候,先比较一下,当前值是不是10。
如果不是,就直接返回false,如果是10,那就将10改为15。
这里比较并修改是原子操作,底层语言保证这一点。
刚刚说的那三个线程,只有一个线程会修改账户的钱,并返回true,其它两个会返回失败,不修改账户的钱。
自旋 简而言之就是死循环,比如 while(true){},名字很高大上,其实就那么回事。
番外篇: park, unpark 不要傻傻分不清
上一篇: AQS简介
下一篇: 加锁源码详细剖析
至此,ReentrantLock加锁解锁,最基本的知识讲完了。