可重入排他锁ReentrantLock源码浅析
1.引子
"ReentrantLock"单词中的“Reentrant”就是“重入”的意思,正如其名,ReentrantLock是一个支持重入的排他锁,即同一个线程中可以多次获得同步状态,常表现为lock()方法的嵌套使用(类似于synchronized代码类嵌套),而在AQS类注释的使用说明中的Mutex是一个不可重入的锁,只要一个线程获得了同步状态,再次tryAcquire(int)返回false。
另外ReentrantLock还支持公平锁和非公平锁的的选择,公平锁是指等待时间长的线程优先获取锁,非公平锁则对所有线程一视同仁;synchronized关键字只支持非同步锁,这是由JVM的本地C++代码决定的。
公平锁虽能解决某些线程长久等待,减少“饥饿”的发生概率,但公平锁没有非公平锁的效率高,因为它要频繁地进行线程上下文切换,一般情况下使用非公平锁。ReentrantLock可以通过设置构造方法的参数来决定使用公平锁或非公平锁,其默认的无参构造方法创建的是一个非公平锁。
ReentrantLock与synchronized的区别对比在以前的帖子Lock接口简介中已经说明过了,这里不再赘述。
2.类结构
由类结构图可以看出,ReentrantLock类中有Sync、FairSync、NofairSync这三个静态内部类。Sync是一个继承于AQS的一个抽象类(AQS相关的内容在之前的帖子),它表示公平锁与非公平锁的通用或共同之处的抽象。FairSync 和NofairSync都继承自Sync,分别表示公平锁、非公平锁.它们两者的类定义差别很小,只有尝试获取锁的方法不同,FairSync使用自已定义的tryAcquire(int),而NotFairSync将tryAcquire(int)委托给父类Sync的nonfairTryAcquire(int)方法实现,而两者尝试释放锁的方法都是继承父类Sync的tryRelease(int)
ReentrantLock的构造方法
ReentrantLock有一个Sync类型的成员变量sync,这个成员变量在构造方法中被实例化。无参的构造方法,将sync实例化为一个NonfairSync对象,此时的ReentrantLock表示一个非公平锁。带一个布尔型参数的构造方法,根据参数就会创建相应的公平锁/非公平锁。
private final Sync sync;
public ReentrantLock() {
sync = new NonfairSync();
} public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L; /**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
abstract void lock(); /**
* 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;
} 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);
}
setState(c);
return free;
} protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
} final ConditionObject newCondition() {
return new ConditionObject();
} // Methods relayed from outer class final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
} final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
} final boolean isLocked() {
return getState() != 0;
} /**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
Sync源代码
Sync的一些其他方法
//当前线程是独占线程
protected final boolean isHeldExclusively() { return getExclusiveOwnerThread() == Thread.currentThread();
}
//给当前锁绑定一个新的条件
final ConditionObject newCondition() {
return new ConditionObject();
} //获得锁的线程
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
//锁被当前线程获取重复获取的次数
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
//锁否已经被某一线程获取到了
final boolean isLocked() {
return getState() != 0;
} /**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
3.可重入的实现机制
锁的可重入是指,某线程在获取到锁之后还能获取到此锁而不会被此锁给阻塞。要保证这点要解决这两个方面的问题:
1) 线程再次获取锁。当要确定当前线程是否已经获取到了锁,如果是,那么再次获取锁也必须是成功的。代码的实现:每次获取锁将AQS的state属性自增,state表示锁被线程获取到的次数。
2) 锁释放。一线程获取了n次锁,那么也需要经过n次释放,锁才能完成审美观点释放,其他线程才能获取到此锁。代码实现思路:每次释放锁让AQS的state属性自减,当state为0时,表明锁被完全释放了。
非公平锁是默认实现,这里以非公平锁为例。
非公平锁获取锁lock()调用的底层方法nonfairTryAcquire(int)
/**
* 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)) {//cas成功,当前线程获取同步状态成功
setExclusiveOwnerThread(current);//设置当前线程为锁的独占线程
return true;
}
}
/**
*当前线程是独占线程,即当前线程之前已经获取到了同步状态.
* 进入重入处理
*/
else if (current == getExclusiveOwnerThread()) {
/**
* nonfairTryAcquire(int)方法的被调用链上层是acquire(1),这里的acquires为1
* 相当于state自增
*/
int nextc = c + acquires;
if (nextc < 0) //nextc超过int类型表示的最大范围
throw new Error("Maximum lock count exceeded");
setState(nextc);//将state属性自增
return true;
}
return false;
}
此方法的基本逻辑是:如果没有任何线程获取到此锁,尝试CAS尝试更新state,若此CAS更新成功,则成功获取锁并将当前线程设置为锁的占有线程,若CAS更新抢购,则获取锁失败。若当前线程获取之前已经获取到此锁,则将重复获取到锁的次数state自增。
非公平锁(和公平锁)释放锁的方法unlock()底层调用父类Sync的tryAcquire(int)
protected final boolean tryRelease(int releases) {
int c = getState() - releases;//传入的参数releases为1
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();//当前线程不是独占线程,即当前线程没获取到同步状态,怎么能释放同步状
boolean free = false;
if (c == 0) {
/**
* 锁当前被重复获取的次数为0,锁已经被彻底释放了,其他线程能获取此锁了
*/
free = true;
setExclusiveOwnerThread(null);
}
setState(c);//state属性自减
return free;
}
此方法的基本逻辑是:如果该锁被获取了n次,那么前(n-1)次tryRelease(int releases)方法必须返回false,而只有同步状态完全释放了,才能返回true。可以看到,该方法将同步状态是否为0作为最终释放的条件,当同步状态为0时,将占有线程设置为null,并返回true,表示释放成功。
4.公平锁与非公平锁
获取同步状态的所有线程都经AQS的静态内部类Node包装成一个Node对象,所有线程都在这由Node构建的先进先出的同步队列中。如果锁是公平锁,那么锁的获取顺序就应该符请求的绝对时间顺序,先请求锁的线程优先获取锁,即也就是先进先出。
公平锁获取锁调用的底层方法tryAcquire(int)
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
/*
*与nonfairTryAcquire(int)方法相比,此处有些不同,
* 只是多了"hasQueuedPredecessors()",没有前驱节点,才进行CAS更新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;
}
可以看出:此方法与nonfairTryAcquire(int)方法相似,只是多了”是否没有前驱节点的判断",只有在没有其他线程比当前线程等待更久的情况下当前线程才会尝试获取锁。
下面进行重入和(非)公平的相关测试
LockTest类中自定义一个锁CustomLock,它继承自ReentrantLock,与ReentrantLock相比,只添加一个"getQueuedThreadNames()"用来返回在同步队列中等待的线程名。
package juc; import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock; public class LockTest {
private static final CustomLock fairLock = new CustomLock(true);
private static final CustomLock unfairLock = new CustomLock(false); public static void printText(boolean fair) {
final CustomLock lock = fair ? fairLock : unfairLock;
lock.lock();
try {
Thread t = Thread.currentThread();
String pName = t.getName();
System.out.print("线程[" + pName + "]第1次重入。"); lock.lock();
try {
System.out.print("线程[" + pName + "]第2次重入。");
System.out.println("阻塞的线程有" + lock.getQueuedThreadNames());
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
} public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
//非公平锁测试
new Thread(() -> {
LockTest.printText(false); LockTest.printText(false);
LockTest.printText(false); }, "pid" + (i + 1)).start();
//公平锁测试
// new Thread(() -> {
// LockTest.printText(true);
//
// LockTest.printText(true);
// LockTest.printText(true);
//
// }, "pid" + (i + 1)).start(); }
} private static class CustomLock extends ReentrantLock {
CustomLock() {
super();
} CustomLock(boolean fair) {
super(fair);
} List<String> getQueuedThreadNames() {
Collection<Thread> threads = super.getQueuedThreads();
List<String> tNames = new ArrayList<>(threads.size());
threads.forEach((thread) -> tNames.add(thread.getName()));
return tNames;
}
}
}
LockTest
控制台打印输出
非公平锁 | |
公平锁 |
可以明显看出:非公平性锁出现了一个线程连续获取锁的情况,而公平性锁每次都是从同步队列中的第一个节点获取到锁(等待时间最久的线程)。
非公平锁可能存在的问题:
在nonfairTryAcquire(int acquires)方法,当一个线程请求锁时,只要获取了同步状态即成功获取锁。在这个前提下,刚释放锁的线程再次获取同步状态的几率会非常大,使得其他线程只能在同步队列中等待。非公平锁可能造成某些线程总是连续获取到锁,而一些线程长期获取不到锁。虽然一些线程获取不到锁,会造成线程“饥饿”,但同一个线程连续获取锁,却减少了线程上下文切换造成的资源消耗,整体上能提高系统的吞吐量。
参考:《Java并发编程的艺术》方腾飞
可重入排他锁ReentrantLock源码浅析的更多相关文章
- concurrent(三)互斥锁ReentrantLock & 源码分析
参考文档:Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock:http://www.cnblogs.com/skywang12345/p/3496101.html Reentr ...
- ReentrantLock源码分析--jdk1.8
JDK1.8 ArrayList源码分析--jdk1.8LinkedList源码分析--jdk1.8HashMap源码分析--jdk1.8AQS源码分析--jdk1.8ReentrantLock源码分 ...
- Java并发之ReentrantLock源码解析(二)
在了解如何加锁时候,我们再来了解如何解锁.可重入互斥锁ReentrantLock的解锁方法unlock()并不区分是公平锁还是非公平锁,Sync类并没有实现release(int arg)方法,这里会 ...
- ReentrantLock(重入锁)的源码解析
转自:从源码角度彻底理解ReentrantLock(重入锁)](https://www.cnblogs.com/takumicx/p/9402021.html)) 公平锁内部是FairSync,非公平 ...
- ZooKeeper 分布式锁 Curator 源码 02:可重入锁重复加锁和锁释放
ZooKeeper 分布式锁 Curator 源码 02:可重入锁重复加锁和锁释放 前言 加锁逻辑已经介绍完毕,那当一个线程重复加锁是如何处理的呢? 锁重入 在上一小节中,可以看到加锁的过程,再回头看 ...
- ZooKeeper 分布式锁 Curator 源码 03:可重入锁并发加锁
前言 在了解了加锁和锁重入之后,最需要了解的还是在分布式场景下或者多线程并发加锁是如何处理的? 并发加锁 先来看结果,在多线程对 /locks/lock_01 加锁时,是在后面又创建了新的临时节点. ...
- 第六章 ReentrantLock源码解析2--释放锁unlock()
最常用的方式: int a = 12; //注意:通常情况下,这个会设置成一个类变量,比如说Segement中的段锁与copyOnWriteArrayList中的全局锁 final Reentrant ...
- 死磕 java同步系列之ReentrantLock源码解析(二)——条件锁
问题 (1)条件锁是什么? (2)条件锁适用于什么场景? (3)条件锁的await()是在其它线程signal()的时候唤醒的吗? 简介 条件锁,是指在获取锁之后发现当前业务场景自己无法处理,而需要等 ...
- ReentrantLock 锁释放源码分析
ReentrantLock 锁释放源码分析: 调用的是unlock 的方法: public void unlock() { sync.release(1); } 接下来分析release() 方法: ...
随机推荐
- VUE - 路由跳转时设置动画效果
/* 为对应的路由跳转时设置动画效果 */ <transition name="fade"> <router-view /> & ...
- jvm问题汇总
1.软引用.弱引用.虚引用-他们的特点及应用场景?
- uboot源码分析2-启动第二阶段
一.背景知识 1.uboot第二阶段应该做什么? 概括来讲uboot第一阶段主要就是初始化了SoC内部的一些部件(譬如看门狗.时钟),然后初始化DDR并且完成重定位. 由宏观分析来讲,uboot的第二 ...
- maven加载ojdbc14报错
问题复现步骤: 1.在pom.xml里面添加ojdbc14的依赖,代码如下: <dependency> <groupId>com.oracle</groupId> ...
- 使用 json 模块,使json数据格式与Python字典dict数据格式互相转换,获取数据更加方便
一.定义 JSON 是一种数据格式 使用 javaScript (Java 死鬼破特)对象表示法 二.特点 1.JSON 与 XML格式数据的区别 ====== 两种格式的数据,都是跨语言,跨平台 c ...
- 我的博客 Hexo 还是Jekyll
我的博客 Hexo 还是Jekyll 标签(空格分隔): 博客 很喜欢找一些博客主题,目前发现几个比较不错的 Hexo: 阿里中间件 我的个人博客-Material主题 我的个人博客-Fluid主题 ...
- apk安装失败的25中原因
名称 Value 描述 INSTALL_FAILED_ALREADY_EXISTS -1 已经安装 INSTALL_FAILED_INVALID_APK -2 APK文件是无效的 INSTALL_FA ...
- C++面试常见问题——08const关键字
const 类内定义 类型名 函数名(参数列表) const{ //函数体: } 类外定义 类内申明 类型名 函数名(参数列表): 类外定义 类型名 类名::函数名(参数列表){ //函数体: ...
- 从0到1完成微信小程序开发(2)
一,小程序的文件结构 小程序包含一个描述程序的app和多个描述各自页面的page 一个小程序主体部分由三个文件组成,必须放在项目的根目录,如下: 一个小程序页面由四个文件组成,分别是: 下面是一个单页 ...
- Python实战案例:这是你见过的最详细的JS加密登录某博
0x00 抓包分析 简单的搜索之后发现,很多参数都是登陆上面这个请求返回的值,这个请求在输入完账号光标到达密码框时就会生成! 0x01 加密逻辑分析 搜索su=可以很快找到加密的位置,上图看到e.su ...