博客地址:https://monkeysayhi.github.io/2017/12/05/%E6%BA%90%E7%A0%81%7C%E5%B9%B6%E5%8F%91%E4%B8%80%E6%9E%9D%E8%8A%B1%E4%B9%8BReentrantLock%E4%B8%8EAQS%EF%BC%881%EF%BC%89%EF%BC%9Alock%E3%80%81unlock/
ReentrantLock和ReentrantReadWriteLock从类的形式上看并没有关联,但是底层都是采用AQS的实现
我们知道我们JDK 的锁都是基于AQS
而在其上面主要有读写锁(即所谓的独占锁和非独占锁)
方法也只有lock 或者unlock
除了锁 还有condition
这些锁都支持可重入
公平锁和非公平锁的最大区别就是:非公平锁是先尝试获取锁,获取不到在按照公平锁的方法进入队列排队获取
锁为什么不能升级--升级会死锁,目的也是为了数据可见性,如果读锁已经被多个线程获取,其中任意线程成功获取了写锁并且更新了数据,这个更新对其他已经获取到读锁的线程是不可见的。
当有线程获取读锁时,不允许再有线程获得写锁
当有线程获得写锁时,不允许其他线程获得读锁和写锁
为什么要支持锁降级
降级的过程为持有写锁,把持住不释放并获取读锁,然后再释放写锁,如果释放写锁再获取读锁则不能称之为锁降级。支持降级的原因是为了实现读写连续,减少线程切换的开销。如果不能降级的话,A线程在对数据修改完毕后立马释放写锁,假如A线程后续紧跟一个读的操作,在获取读锁之前B线程拿到了写锁,由于读写不能并发,A线程就只能阻塞
首先我们分析下ReentrantLock
获取锁,如果获取不到则会一直阻塞
不会响应中断
readLock.lock();
尝试获取锁,获取不到就返回 不会响应中断
readLock.tryLock();
获取锁,如果获取不到则会一直阻塞
会响应中断
readLock.lockInterruptibly();
尝试获取锁,获取不到就等待一段时间 会响应中断
readLock.tryLock(10,TimeUnit.SECONDS);
ReentrantLock的lock源码
public final void acquire(int arg) {
首先尝试获取锁失败,则加入等待队列,acquireQueued返回true代表
在等待获取锁的过程中中断过
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
因为之前中断后,AQS恢复了中断标识,这边自我中断下吧中断标识
设置为true。这样如果有业务逻辑需要判断中断标识,可以得知是否中断过
selfInterrupt();
}
非公平锁是先尝试获取锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
因为是独占锁
int c = getState();
当锁没有被持有的时候,直接去获取 不需要管是否有其他人在等待
if (c == 0) {
设置state
if (compareAndSetState(0, acquires)) {
设置独占标识的线程
setExclusiveOwnerThread(current);
return true;
}
}如果当前线程等于独占标识的线程
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
更新state,如果state小于0 代表重入超过最大值了
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
获取失败
return false;
}
公平的获取锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
如果这个锁还没人获取
if (c == 0) {
如果不存在等待队列且设置state成功 即获取了锁,设置独占标识的线程
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;
}
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
h!=t 代表最少有2个元素 一个是head 一个是tail
return h != t &&
第一个如果为true 正好执行到tail了 即s=tail节点
否则只要判断下s.thread不等于当前线程
((s = h.next) == null || s.thread != Thread.currentThread());
}
按照锁的模式把当前线程包装成节点
注意初始化之后head和老的tail节点的next都是第一个正常节点,也就是新的tail节点
而不是新的head-->old tail-->新的tail 真正的是head-->新的tail 和old tail-->新的tail
其中head和oldtail 是同一个对象的两种不同的引用 也就是说最终就是head-->tail tail是正常节点
head是dummpy节点
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
首先利用cas直接放入尾部
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
for循环利用cas放入队列
enq(node);
return node;
}
这边将tail赋值给t非常好,当我们将node设置尾部tail成功时候
t还是只想tail前一个继续操作next绑定node,就算这个时候并发另外一个线程很快
也设置tail成功也不影响前面的设置
当head=tail说明刚初始化好
private Node enq(final Node node) {
for (;;) {
获取尾部
Node t = tail;
如果尾部不存在,说明还没初始化,先初始化队列
if (t == null) { // Must initialize
设置head节点 如果成功了就把head和tail 赋值为一个new出来的node节点,然后在重新循环将该队列设置到尾部,刚初始化的节点是没用的后期都会移除
if (compareAndSetHead(new Node()))
tail = head;
} else {
尝试把节点加入尾部,这边存在 pre一致 但是原始的tail的next 还是null的情况
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
循环等待该node获取锁,并记录自己被中断
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
获取node的前置节点,前置节点有可能是正常的node
也有可能是head和tail 这种刚初始化dummpy节点
final Node p = node.predecessor();
如果前置节点就是head,那么就尝试获取下锁(这时候程序在运行的情况分为
刚加入队列,或者被head节点唤醒且正在释放锁或者还未释放完)
if (p == head && tryAcquire(arg)) {
获取锁成功把这个node设置为head
setHead(node);
p.next = null; // help GC
failed = false;
返回
return interrupted;
}
如果p不等head 或者获取锁失败,
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
执行到这一步且failed=true 代表发送了异常,取消获取
if (failed)
cancelAcquire(node);
}
}
waitStatus四种状态
SIGNAL:标识这个节点如果后期获取到锁 在释放的时候需要唤醒他的后继节点
CANCELLED:因为超时或者被中断,其线程不会再阻塞
CONDITION:这个还记得咱们的Condition这个对象吗?这个代表节点还在condition队列中 还未进入咱们的同步队列
PROPAGATE:调用共享锁的时候的是否应该无条件的传播
head节点就是signal其后面的正常节点就是condition
0:刚创建的节点都是0,这个方法就是把所以的0都转换为signal
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
假如是刚初始化的时候 那么head的waitstatus是0
int ws = pred.waitStatus;
当SIGNAL时候代表head节点正在持有锁 还没释放锁 所以后置节点应该沉睡
等待head把他唤起
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
如果大于0说明节点是被取消的 应该移除这些节点
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
这边有个问题 pre这个废弃节点如果想完全移除出链表
不需要 pred.next = null 因为cancelAcquire(node); 已经
执行过这段代码了
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
一般到这一步了说明是0或者是PROPAGATE 尝试把head节点设置为
SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
阻塞,然后返回释放interupt,是的话设置interupt标识
private final boolean parkAndCheckInterrupt() {
该线程沉睡
LockSupport.park(this);
return Thread.interrupted();
}
取消获取失败的节点
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return;
将该节点的thread置空
node.thread = null;
如果node的前置节点是取消状态,直接跳过
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
前置节点的next正常没有并发的情况下就是我们的node节点
Node predNext = pred.next;
将节点状态变为取消
node.waitStatus = Node.CANCELLED;
如果当前节点就是tail 那么我们把tail设置为pre(pre有可能是head 也有可能是其他的节点)
if (node == tail && compareAndSetTail(node, pred)) {
把这个节点的next设置为null
compareAndSetNext(pred, predNext, null);
} else {
说明不是tail
我们看看如果pre不是head 且pre是SIGNAL或者 ws<0 我们把pre的waitstatus设置为signal
且pre的thread不为空
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
获取node的下一个正常节点塞入pre的next
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
如果是head 那么我们就需要唤醒下一个节点
unparkSuccessor(node);
}
把脱离的节点从链表中拿出来
node.next = node; // help GC
}
}
private void unparkSuccessor(Node node) {
设置waitstatus为0,因为他的下一个节点不需要signal,我们正在signal
设置为0失败也没关系
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
如果节点的下一个是null 或者为取消状态
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
从尾部开始寻找,node节点不等于当前且waitstatus小于等于0
假如你从前往后遍历的动作发生在enq方法中的cas之后和赋值之前最后一个节点是漏掉的
在enq中我们是先把新的tail的pre和老的tail 链接在一起 等cas成功了 在设置老tail的next和新的tail
如果我们从前往后遍历的时间刚好发生在cas之和赋值之前 那么我们tail节将被遗漏
但是我们这边唤醒最后一个可用的 也不用怕,因为其前置节点不是head 所以其不会获得锁
但是会帮我们清理取消的节点 这样就可以正常的获取下一个节点进行唤醒获得锁的操作
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
唤醒该线程
if (s != null)
LockSupport.unpark(s.thread);
}
lockInterruptibly
public final void acquireInterruptibly(int arg)
throws InterruptedException {
如果检测到线程中的 抛出异常
if (Thread.interrupted())
throw new InterruptedException();
获取线程
if (!tryAcquire(arg))
这个过程我们之前分析过至少这边如果检测到中断就直接抛出
我们正常的节点唤醒是通过unsafe的unpark方法
doAcquireInterruptibly(arg);
}
ReentrantLock的trylock 底层默认的是采用unfaire,我们上面已经分析过了,获取不到直接返回
ReentrantLock的trylock 携带时间的参数底层也有多种实现 ,可以响应中断
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
我们主要分析下如何做到时间控制
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
计算超时时间
final long deadline = System.nanoTime() + nanosTimeout;
添加节点
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
开始获取锁
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
如果未获取到通过LockSupport.parkNanos来沉睡
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
ReentrantLock的unlock
public final boolean release(int arg) {
尝试释放节点
if (tryRelease(arg)) {
释放成功的话把,唤醒head节点的下一个
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
如果当前线程不是获取独占锁的线程 抛出异常
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
如果锁都释放了 则解除独占状态
free = true;
setExclusiveOwnerThread(null);
}
重新设置state
setState(c);
return free;
}
再分析ReentrantReadWriteLock.WriteLock
ReentrantReadWriteLock.WriteLock 的lock
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
主要是tryAcquire与reentrant不一样我们分析下
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
当c!=0 w=0 代表共享锁被获取了 但是独占锁没有,而我们此时是获取独占锁的
所以直接返回,也就是说代码中直接拒绝 了我们 不允许 读锁降级为写锁
当w!=0 且不是独占锁的线程也不可获取锁
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
if (w == 0 || current != getExclusiveOwnerThread())
return false;
如果获取锁超过了最大值 抛出异常
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
设置state状态
setState(c + acquires);
return true;
}
对于公平锁来说 当锁还未被获取,且有队列存在 那么write应该block
对于非公平来说直接返回false
if (writerShouldBlock() ||
尝试获取锁
!compareAndSetState(c, c + acquires))
return false;
获取成功设置独占标识
setExclusiveOwnerThread(current);
return true;
}
ReentrantReadWriteLock.ReadLock的lock
共享锁---当我们的写锁被其他线程持有 那么我们是不可以获取读锁,写锁排斥任何的锁
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
当前获取的锁
int c = getState();
如果写锁存在而且非本线程持有 直接返回
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
公平方面主要就是看看是否存在等待队列
非公平方式的实现主要就是判断head的下一个是否是写锁,也就是说如果是血锁,就不去抢,否则就去抢锁
if (!readerShouldBlock() &&
r < MAX_COUNT &&
SHARED_UNIT可以留意
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
如果是第一次获取 设置 firstReader 和firstReaderHoldCount
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
如果是第一个线程重复获取 更新 firstReaderHoldCount
firstReaderHoldCount++;
} else {
代表是其他线程获取
cachedHoldCounter 代表最后一个线程的对象,依赖threadlocal存储着重入的次数
HoldCounter rh = cachedHoldCounter;
如果是第一次被其他线程获取或者多次被其他线程获取 (通过对比id)
if (rh == null || rh.tid != getThreadId(current))
生成一个新的cachedHoldCounter
cachedHoldCounter = rh = readHolds.get();
是当前线程且rh不等于null
else if (rh.count == 0)
readHolds.set(rh);
给count增加1
rh.count++;
}
return 1;
}
轮询的方式重复了上面的步骤
return fullTryAcquireShared(current);
}
执行到这一步要么前面是head的next存在写锁(非公平)
要么是就是公平锁下面有队列
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();
先检测写锁是否被其他线程获取,如果是直接返回
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
} else if (readerShouldBlock()) {
如果write应该block 如果firstReader == current
可以尝试去获取读锁
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
} else {
如果rh==null,检测最后一个获取读锁的线程是否有问题
if (rh == null) {
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove();
}
}
这边如果获取锁不可能count=-1 直接返回
if (rh.count == 0)
return -1;
}
}
超出获取的最大值 抛出异常
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
重复了我们上面的步骤
if (compareAndSetState(c, c + SHARED_UNIT)) {
if (sharedCount(c) == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
private void doAcquireShared(int arg) {
加入节点
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
如果前置节点是head 就获取锁,
if (p == head) {
int r = tryAcquireShared(arg);
获取成功就设置head
if (r >= 0) {
设置head并释放写锁
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
readerShouldBlock
公平方面主要就是看看是否存在等待队列
非公平方式的实现主要就是判断head的下一个是否是写锁,也就是说如果是血锁,就不去抢,否则就去抢锁
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
共享锁的是否就是,先去消除线程中持有锁的计数,然后在修改state
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;
}
}