共计 6025 个字符,预计需要花费 16 分钟才能阅读完成。
咱们能够理解到它是一个可重入锁, 上面咱们就一起看一下它的底层实现~
构造函数
咱们在应用的时候,都是先 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 可重入锁体验在哪里。