前言
目前正在出一个Java多线程专题
长期系列教程,从入门到进阶含源码解读
, 篇幅会较多, 喜欢的话,给个关注❤️ ~
源码剖析
上节带大家学习了它的基本使用,我们可以了解到它是一个可重入锁
,下面我们就一起看一下它的底层实现~
构造函数
我们在使用的时候,都是先new
它,所以我们先看下它的构造函数
,它主要有两个:
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
复制代码
从字面上看,它们之间的不同点在于fair
,翻译过来就是公平的意思,大体可以猜到它是用来构建公平锁和非公平锁
,在继续往下看源码之前,先给大家科普一下这两种锁。
公平锁 & 非公平锁
公平锁
多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。(例如银行办业务取号)
这种锁的优点很明显,每个线程都能够获取资源,缺点也很明显,如果某个线程阻塞了,其它线程也会阻塞,然而cpu唤醒开销很大,之前也给大家讲过
非公平锁
多个线程都去尝试获取锁,获取不到就进入等待队列,cpu也不用去唤醒
优缺点正好和上边相反,优点减少开销,缺点也很明显,可能会导致一直获取不到锁或长时间获取不到锁
好,有了基本概念之后,我们继续往下看
NonfairSync
首先,我们看下非公平锁,默认情况下,我们申请的都是非公平锁,也就是new ReentrantLock()
,我们接着看源码
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
复制代码
它继承了Sync
,Sync是一个内容静态抽象类:
abstract static class Sync extends AbstractQueuedSynchronizer {...}
复制代码
分为公平和非公平,使用AQS
状态来表示持锁的次数,在构造函数初始化的时候都有sync = ...
,我们接着看NonfairSync
。在使用的时候,我们调用了lock.lock()
方法,它是ReentrantLock
的一个实例方法
// 获取锁
public void lock() {
sync.lock();
}
复制代码
实际上内部还是调了sync
的内部方法,因为我们申请的是非公平锁
,所以我们看NonfairSync
下的lock实现:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
复制代码
compareAndSetState
这个方法,是AQS
的内部方法,意思是如果当前状态值等于预期值,则自动将同步状态设置为给定的更新值。此操作具有volatile读写的内存语义。
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
复制代码
可以看到执行lock
方法,会通过AQS
机制计数,setExclusiveOwnerThread
设置线程独占访问权限,它是AbstractOwnableSynchronizer
的一个内部方法,子类通过使用它来管理线程独占
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {}
复制代码
可以看到它是继承了AbstractOwnableSynchronizer
。下面接着看,我们说如果实际值等于期望值会执行上边的方法,不期望的时候会执行acquire(1)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
复制代码
这个方法以独占模式获取,忽略中断,它会尝试调用tryAcquire
,成功会返回,不成功进入线程排队,可以重复阻塞和解除阻塞。看下AQS
内部的这个方法
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
复制代码
我们可以看到实现肯定不在这,它的具体实现在NonfairSync
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
复制代码
可以看到它调用了,nonfairTryAcquire
方法,这个方法是不公平的tryLock
,具体实现在Sync内部
,这里我们要重点关注一下
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 返回同步状态值,它是AQS内部的一个方法
// private volatile int state;
// protected final int getState() {
// return state;
// }
int c = getState();
if (c == 0) {
// 为0就比较一下,如果与期望值相同就设置为独占线程,说明锁已经拿到了
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 否则 判断如果当前线程已经是被设置独占线程了
else if (current == getExclusiveOwnerThread()) {
// 设置当前线程状态值 + 1 并返回成功
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 否则返回失败 没拿到锁
return false;
}
复制代码
好,我们再回过头看下 acquire
public final void acquire(int arg) {
// 如果当前线程没有获取到锁 并且 在队列中的线程尝试不断拿锁如果被打断了会返回true, 就会调用 selfInterrupt
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
复制代码
selfInterrupt
很好理解,线程中断
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
复制代码
其实我们关注的重点是这个方法acquireQueued
,首先关注一下入参,它内部传入了一个addWaiter
,最后它回NODE
节点
private Node addWaiter(Node mode) {
// mode 没啥好说的就是一个标记,用于标记独占模式 static final Node EXCLUSIVE = null;
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
复制代码
我们可以大体从猜到,Node
是一个等待队列的节点类,是一个链表结构,之前我们讲FutureTask
源码的时候也遇到过这种结构,它通常用于自旋锁
,在这个地方,它是用于阻塞同步器
+------+ prev +-----+ +-----+
head | | <---- | | <---- | | tail
+------+ +-----+ +-----+
复制代码
好,下面我们关注一下 acquireQueued
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
// 默认是 false
boolean interrupted = false;
// 进入阻塞循环遍历 线程队列
for (;;) {
// 返回前一个节点
final Node p = node.predecessor();
// 判断如果前一个节点是头部节点,并且拿到锁了,就会设置当前节点为头部节点
if (p == head && tryAcquire(arg)) {
setHead(node);
// 这里可以看到注释 help gc ,
p.next = null; // help GC
failed = false;
return interrupted;
}
// 检查并更新未能获取的节点的状态。如果线程应该阻塞,则返回 true 并且线程中断了
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
// 如果失败 取消正在尝试获取的节点
if (failed)
cancelAcquire(node);
}
}
复制代码
从上面的源码来看,在体会一下上面讲的非公平锁
的概念,是不是更好理解一些,然后就是释放锁unlock
,这个方法我们可以看到是ReentrantLock
下的一个实例方法,所以公平锁的释放锁也是调的这个方法,其实最终可以猜到调用的还是sync
的方法
public void unlock() {
sync.release(1);
}
复制代码
Sync
继承AQS
,release是AQS
的内部方法
public final boolean release(int arg) {
// 尝试释放锁 tryRelease 在Sync内部
if (tryRelease(arg)) {
Node h = head;
// 如果节点存在 并且状态值不为0
if (h != null && h.waitStatus != 0)
// 唤醒下个节点
unparkSuccessor(h);
return true;
}
return false;
}
复制代码
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 可以看到调用了 LockSupport来唤醒
LockSupport.unpark(s.thread);
}
复制代码
我们再看下tryRelease
, 同样这个实现在Sync
内
protected final boolean tryRelease(int releases) {
// 同样释放锁的时候 依然使用 AQS计数
int c = getState() - releases;
// 判断当前线程是否是独占线程,不是抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 如果是0 表示是释放成功
if (c == 0) {
free = true;
// 并且把独占线程设为null
setExclusiveOwnerThread(null);
}
// 更新状态值
setState(c);
return free;
}
复制代码
FairSync
公平锁FairSync
的区别在于,它的获取锁的实现在它的内部,Sync
默认内部实现了非公平锁
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
// 这个方法最终调用 tryAcquire
final void lock() {
acquire(1);
}
// 公平锁的实现
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 这边和非公平锁的实现有些相似 同样判断状态
if (c == 0) {
// 判断排队队列是否存在, 不存在并且比较期望值
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// 设置独占线程 并返回成功
setExclusiveOwnerThread(current);
return true;
}
}
// 这边和上面类似
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
复制代码
它的实现比较简单,通过实现可以发现,它按照申请锁的顺序来获取锁,排第一的先拿到锁,在结合上面的概念理解一下,就很好理解了.
释放锁unlock
,上面我们已经讲过了~
结束语
本节内容可能有点多,主要是看源码,可以打断点自己调一下, 举一反三,通过源码去理解一下什么是公平锁和非公平锁
, ReentrantLock
可重入锁体验在哪里。下一节,带大家理解一下CAS与原子操作
的概念 ~