ReentrantLock知识点梳理
时间:2022-07-23
本文章向大家介绍ReentrantLock知识点梳理,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
接下来几篇文章会对JUC并发包里面的锁工具类做下梳理,如:ReentrantLock、
ReentrantReadWriteLock、StampedLock
一、基本信息
Lock 用于解决互斥问题,即同一时刻只允许一个线程访问共享资源;Condition 用于解决同步问题(即线程之间如何通信、协作)
ReentrantLock 类结构:
- ReentrantLock 实现了Lock接口
- 内部类:
class Sync extends AbstractQueuedSynchronizer
- 内部类:
class NonfairSync extends Sync
- 内部类:
class FairSync extends Sync
- 内部类:
- 可重入锁
- 默认是非公平模式
- 主要方法:
- lock():阻塞模式来获取锁
- lockInterruptibly:阻塞式获取锁,支持中断
- tryLock():非阻塞模式尝试获取锁
- tryLock(long timeout, TimeUnit unit):同上,支持时间设置
- unlock():释放锁
- newCondition():创建条件变量
- getHoldCount():当前线程对该锁的计数次数
- isHeldByCurrentThread():锁是否被当前线程持有
- isLocked():锁是否已经被某个线程持有
- getQueuedThreads():获取排队的线程列表
AbstractQueuedSynchronizer 类结构:
- 内部结构:
- transient volatile Node head:
- transient volatile Node tail:
- volatile Thread thread:
- volatile int state:反映锁的持有情况,当前线程获得锁,对其+1,释放锁对其 -1
- 核心
- 一个 volatile 的整数成员表征状态,同时提供了 setState 和 getState 方法
- 一个先进先出(FIFO)的等待线程队列,以实现多线程间竞争和等待
- 底层基于 CAS 的基础方法(Unsafe类),以及各种期望具体同步结构去实现的 acquire/release 方法
Condition 类结构:
当使用Lock来保证线程同步时,需使用Condition条件变量来使线程保持协调。Condition实例被绑定在一个Lock的对象上,使用Lock对象的方法newCondition()获取Condition的实例。Condition提供了下面三种方法,来协调不同线程的同步:
- await():当前线程锁释放,并进入等待池中,直到其他线程调用该Condition的signal()或signalAll()方法唤醒该线程,重新去请求锁,拿到锁后,才可以继续进行后面操作。
- signal():唤醒在此Lock对象上等待的单个线程。
- signalAll():唤醒在此Lock对象上等待的所有线程。
synchronized与ReentrantLock比较:
- 相同:
- 都是可重入锁
- 区别:
- synchronized 是Java的一个内置关键字,而ReentrantLock是Java的一个类。
- synchronized只能是非公平锁。而ReentrantLock可以实现非公平锁、公平锁两种。
- synchronized 采用阻塞式,如果申请不到资源,线程直接进入阻塞状态,啥都干不了,也释放不了线程已经占有的资源(此时如果死锁,无法打破“不可剥夺条件”)。相反Lock更加灵活,支持非阻塞地获取锁、支持超时、支持响应中断。详细内容
- synchronized采用隐式加锁,Lock采用显式加锁lock()、unLock()
- synchronized会自动释放锁,而ReentrantLock不会自动释放锁,必须手动释放,否则可能会导致死锁。
- synchronized只有一个Condition条件变量。Lock支持多个。详细过程
二、源码分析
1、获取锁 lock()
java.util.concurrent.locks.ReentrantLock#lock
public void lock() {
// sync,是构造器传入的,支持非公平模式、公平模式
sync.lock();
}
java.util.concurrent.locks.ReentrantLock.NonfairSync#lock
final void lock() {
// 借助 unsafe 原子性对 state 加1。如果初始值为0,表示没有线程占有锁
if (compareAndSetState(0, 1))
// 设置当前线程独占锁
setExclusiveOwnerThread(Thread.currentThread());
else
// 尝试获取锁
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 获取当前 AQS 内部状态量
int c = getState();
// 0 表示无线程占有,直接用 CAS 修改
if (c == 0) {
// 不检查排队情况,直接争抢
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 可重入锁情况
else if (current == getExclusiveOwnerThread()) {
// state 计数增加
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
如果前面的 tryAcquire 失败,代表着锁争抢失败,进入排队竞争阶段
// 当前线程被包装成EXCLUSIVE排他模式的节点,通过addWaiter方法添加到队列中
private Node addWaiter(Node mode) {
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;
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// 无限循环
for (;;) {
// 当前节点的前一个节点
final Node p = node.predecessor();
// 如果前一个节点是头结点,表示当前节点适合去 tryAcquire
if (p == head && tryAcquire(arg)) {
// if 获取成功,设置当前节点为头节点,出队列
setHead(node);
// 将前面节点对当前节点的引用清空
p.next = null; // help GC
failed = false;
return interrupted;
}
// 如果返回true,需要阻塞
if (shouldParkAfterFailedAcquire(p, node) &&
// 借助sun.misc.Unsafe#park 执行阻塞
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
// 普通挂起。直到另一个持有锁线程释放锁后,触发下一个线程的 sun.misc.Unsafe#unpark
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
2、释放锁 unlock()
public void unlock() {
sync.release(1);
}
}
- go语言网络编程之tcp
- 实现简单的http并发请求,支持:GET、POST、HEAD、PUT
- 【go语言】Goroutines 并发模式(一)
- 【go语言】Goroutines 并发模式(二)
- 从零开始搭建Nginx和Tomcat的web集群环境
- 【翻译】为什么 goroutine 的栈内存无穷大?
- 从PowerVM,KVM到Docker:存储池的配置与调优---第一篇终结(第3子篇)
- goroutine背后的系统知识
- 从PowerVM,KVM到Docker:存储池的配置与调优---第一篇(第2子篇)
- 2017最后一天的学习-TensorFlow
- 从PowerVM,KVM到Docker:存储池的配置与调优---第一篇(第1子篇)
- 36小时,造一个亚马逊无人商店 | 实战教程+代码
- 使用Echarts来实现数据可视化
- 手把手教你用TensorFlow实现看图说话 | 教程+代码
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- golang string和[]byte的对比
- PHP中使用Redis长连接笔记
- 从外部设置传入Go变量
- 图解elasticsearch的_source、_all、store和index
- Stream API
- Lambda表达式
- ES的Query、Filter、Metric、Bucketing使用详解
- Golang的单引号、双引号与反引号
- CentOS配置docker和docker-compose
- 给Linux增加swap内存
- 网鼎杯2018-Fakebook
- 强网杯2019-高明的黑客
- CISCN2019华北赛区Day2-HackWorld
- ZJCTF-NiZhuanSiWei
- xxe漏洞学习