写点什么

jdk 源码系列之 ReentrantLock

用户头像
sinsy
关注
发布于: 2020 年 11 月 10 日
jdk 源码系列之ReentrantLock

最近将 ReentrantLock 学了一遍同时也把源码读了一遍,记录下学习的过程

JDK 源码系列



使用



使用锁机制,来保障线程安全



Lock lock = new ReentrantLock();
lock.lock();
try {
// 受此锁保护的资源块
} finally {
lock.unlock();
}



或者你可以使用 tryLock() 方法,在多线程中,当一个线程释放锁的时候,就尝试去获取锁。



Lock lock = new ReentrantLock();
if (lock.tryLock()) {
try {
// 受此锁保护的资源块
} finally {
lock.unlock();
}
} else {
// 进行其他操作,未被锁保护
}



这种方法,在确保获取锁的时候才会解锁,并且在未获锁时不会尝试去解锁。



如果尝试去获取锁的时候太长,也可以给获取锁的这个过程加上时间,超时则直接中断线程。



public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
// manipulate protected state
}
finally {
lock.unlock();
}
} else {
// perform alternative actions
}
}
}



如果希望当前锁的模块不是立刻执行,也可以调用 await 机制



Lock lock = new ReentrantLock();
lock.lock();
try {
// manipulate protected state
lock.newCondition().await(5, TimeUnit.SECONDS);
}
finally {
lock.unlock();
}



有时候,当遇到过长的业务流程,导致持有的时间太长了,可以考虑打断锁的机制,释放锁。



Lock lock = new ReentrantLock();
lock.lock();
try {
// manipulate protected state
long startTime = System.currentTimeMillis();
long endTime = System.currentTimeMillis();
if (endTime - startTime > 10) {
lock.lockInterruptibly();
try {
} finally {
lock.unlock();
}
}
lock.newCondition().await(5, TimeUnit.SECONDS);
}
finally {
lock.unlock();
}



应用场景比较





源码



先看类



public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;



向上继承了 Lock 接口,以及 Serializable,都是实现了 Lock 的方法。



  • void lock() 获取锁

  • void lockInterruptibly() 中断锁机制

  • boolean tryLock() 其他线程有释放锁,才调用获取锁

  • boolean tryLock(long time, TimeUnit unit) 给定时间内线程是空闲时间,且其他线程有释放锁,才调用获取锁

  • void unlock() 释放锁

  • Condition newCondition() 给锁绑定条件



实例化锁



Lock lock = new ReentrantLock();
Lock lock1 = new ReentrantLock(false);
Lock lock2 = new ReentrantLock(true);



这里,有两个构造,一个是无参构造,一个是传入布尔值。



/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
// 创建一个非公平锁
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
// true 公平锁 false 非公平锁
sync = fair ? new FairSync() : new NonfairSync();
}



这里默认是构造一个非公平锁,也可以直接设置创建什么类型锁,比如公平锁、非公平锁。



我们先看看公平锁。



/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
// 当使用 lock.lock() 的时候调到 同时传入1
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
// 这里 acquires = 1指的是 上锁的状态 0则是未上锁
protected final boolean tryAcquire(int acquires) {
// 获取当前线程对象
final Thread current = Thread.currentThread();
// 是否上锁,首次默认是0
int c = getState();
if (c == 0) {
// hasQueuedPredecessors 是否是当前线程,使用了双向链表的数据结构,做一个 queue,这里的意思大概是到了这个线程可以加锁了。不用排队了
if (!hasQueuedPredecessors() &&
// CAS 比较后修改成功
compareAndSetState(0, acquires)) {
// 将当前线程设置成独占线程
setExclusiveOwnerThread(current);
// 获取锁成功
return true;
}
}
// 独占线程
else if (current == getExclusiveOwnerThread()) {
// nextc = 1
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
// 同步锁的状态 = 1
setState(nextc);
return true;
}
// 其他线程则未获取到锁
return false;
}
}
/**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
// 尝试上锁
if (!tryAcquire(arg) &&
// 先将线程当前线程添加进队列最后一个,然后在判断是否到他上锁了
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 没有则直接打断线程
selfInterrupt();
}
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
// 持有锁的队列里面的线程
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// 一个死循环你判断里面当前谁获得锁
for (;;) {
// 指向下一个节点
final Node p = node.predecessor();
// 队列的第一个,且获取锁,将当前线程变成独占线程
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
// 如果获得到锁,则不需要打断当前线程返回一个 false
return interrupted;
}
// 线程阻塞了
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 中断获取锁的机制
interrupted = true;
}
} finally {
// 如果获得到锁 则不需要取消加锁操作、反之则取消
if (failed)
cancelAcquire(node);
}
}



公平锁,当前的线程会进入一个队列中最后一个,等待到他的上锁。如果当前的线程表示获取到锁,且也是队列的第一个位置。会将自己变成锁的独占线程,只有自己才持有这个锁的对象。同时这里进行了 CAS 的原子交换,设置状态为1。其中 0 为 未上锁状态,1为上锁状态。



非公平锁



/**
* Sync object for non-fair locks
*/
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);
}
}
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
// 这个和公平锁里面差不多
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}



非公平锁,先进行抢占锁,先查看线程是否释放了锁,如果释放了,当前线程则直接上锁,这样的好处就是减少了线程之间的等待,加快了上锁的机制,避免了排队时竞争所导致的延时,提高了性能,如果没有释放锁,则进入到队列的最后一个等待上锁。



小结一下。



释放锁机制。



/**
* Attempts to release this lock.
*
* <p>If the current thread is the holder of this lock then the hold
* count is decremented. If the hold count is now zero then the lock
* is released. If the current thread is not the holder of this
* lock then {@link IllegalMonitorStateException} is thrown.
*
* @throws IllegalMonitorStateException if the current thread does not
* hold this lock
*/
public void unlock() {
// 携带 1
sync.release(1);
}
public final boolean release(int arg) {
// 释放成功则为 true 否则是 false
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
// 唤醒队列后面的线程
unparkSuccessor(h);
// 释放成功
return true;
}
// 释放失败
return false;
}
protected final boolean tryRelease(int releases) {
// c = 0
int c = getState() - releases;
// 如果不是当前线程和不是独占线程
if (Thread.currentThread() != getExclusiveOwnerThread())
// 直接抛出错
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
// 释放锁
free = true;
// 独占线程设置null
setExclusiveOwnerThread(null);
}
// 0
setState(c);
return free;
}



锁的释放,比较简单。将锁状态重新设置回 0,同时独占线程也设置null,之后唤醒后面的队列里面的线程,完成释放。



源码总结



ReentrantLock 创建的时候,默认是非公平锁,不过你也可以在构造的时候,也可以创建一个公平锁。 其中通过 CAS 改变 state 的状态来改变锁的数值, 0 表示有锁可以获取,1 表示锁已被获取,来设置锁的独占线程。



在公平锁的机制中,请求锁的线程会直接排到一个队列中(通过一个双向链表来模拟的队列)的最后一个,去获取锁。



非公平锁的机制中,请求锁的线程首先会先通过 CAS 来改变 state 的锁状态,如果可以改变(0 -> 1),则直接获取到锁,将自身设置成独占锁。这样的好处就减少了一些进队列、加载队列、唤醒线程等性能消耗。如果未能修改到 state 的状态,也会变成公平锁的机制,进入到队列的最后一个,等待到它去获取锁。



锁的释放,将 state 重新设置回 0,同时独占线程(你也可以认为这是持有锁的线程对象)设置null,之后唤醒排在它下个的线程。这一系列步骤做完,则宣告锁的释放。

优缺点

非公平锁,确实性能比较高。不过也有一个显而易见的缺点,我们可以想象一下,当你在排队吃饭的时候,轮到你吃饭的时候,这时候突然来一个人插在你前面,提前打饭了,导致你打饭时间变长了,如果这时候在有几个人在也突然插到你前打饭,又会继续导致你打饭时间变得更长。那如果放到线程里面,突然其他线程提前获取到了锁,那会导致当前线程获取到锁时间变长,而导致线程阻塞,迟迟未获取到锁。



所以得根据业务去选择合适的锁类型,进行上锁,尽可能的避免有一些重要的业务因为上锁而阻塞到。



声明



作者: Sinsy 本文链接:https://blog.sincehub.cn/2020/11/10/jdk-reentrantLock/

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文声明。 如您有任何商业合作或者授权方面的协商,请给我留言:550569627@qq.com



引用





发布于: 2020 年 11 月 10 日阅读数: 1206
用户头像

sinsy

关注

还未添加个人签名 2019.10.18 加入

公众号:编程的那些年 个人博客网站:https://blog.sincehub.cn/

评论 (3 条评论)

发布
用户头像
lock.lockInterruptibly();的用法写错了
2020 年 12 月 01 日 17:26
回复
释放错了?还是选的例子不好?
2020 年 12 月 02 日 09:25
回复
用户头像
顶,也可以适当的推荐一些源码地址或者书籍
2020 年 11 月 11 日 09:53
回复
没有更多了
jdk 源码系列之ReentrantLock