java的ReentrantLock类详解
ReentrantLock 能用于更精细化的加锁的Java类, 通过它能更清楚了解Java的锁机制
ReentrantLock 类的集成关系有点复杂, 既有内部类, 还有多重继承关系
![image-20191123213426815](/Users/dasouche/Library/Application Support/typora-user-images/image-20191123213426815.png)
类的定义
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
..............
- 实现了 Serializable 接口
- 实现了Lock接口, Lock 接口中就定义常用的加锁和释放锁的方法. 是一个基本的接口, 很多的锁类都实现了这个方法
- sync是它的重要成员变量, 加锁和解锁的操作都是通过这个变量实现的, 这个Sync 是一个静态内部类.
加锁的逻辑
ReentrantLock 的lock方法, tryLock方法和unlock方法
public void lock() {
sync.lock();
}
public void unlock() {
sync.release(1);
}
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
从上面的方法可以看出加锁的操作都是交给Sync类来实现的,下面就来看看Sync类
构造方法
/**
* 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) {
sync = fair ? new FairSync() : new NonfairSync();
}
两种构造方法其实是对应这两种锁, 公平锁和非公平锁, 公平锁是依赖FairSync 类来实现的, 非公平锁是依赖NonfairSync来实现的
NonfairSync类详解, 非公平获取锁的真正操作类
静态内部final类
/**
* 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);
}
}
NonfairSync类实现了虚拟类Sync 的lock 和tryAcquire 方法
而Sync 又继承了AbstractQueuedSynchronizer 虚拟类, AbstractQueuedSynchronizer 是一个很重要的类内部维护了 等待获取锁的线程队列
lock方法
加锁方法, 方法内有两个分支逻辑,
- 先判断是否是无锁状态并尝试加锁
compareAndSetState
, 如果无锁且加锁成功则把当前线程加入AQS队列中setExclusiveOwnerThread
. - 尝试获取锁没有成功, 就调用acquire 方法来获取锁
compareAndSetState方法判断
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
内部使用unsafe.compareAndSwapInt方法, 这方法是native方法. 是把比较和设置两步作为原子操作的方法.
setExclusiveOwnerThread方法
这个方法就是 AbstractQueuedSynchronizer 类的方法, 左右就是把当前线程标记成获取锁的线程
acquire方法, 真正获取锁的方法.
这个是NonfairSync 父类的父类AbstractQueuedSynchronizer 类的方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 如果获取到锁就会跳出这个循环, 如果获取不到锁, 会一直阻塞在这里
selfInterrupt();
}
内部一个if判断后的调用selfInterrupt方法. 重点在if判断中逻辑.
if中首先调用tryAcquire方法, tryAcquire 在AbstractQueuedSynchronizer 中 没有实现逻辑只是抛出异常, 所以具体的逻辑实在子类NonfairSync 中,
NonfairSync.tryAcquire方法
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
可以看到调用了nonfairTryAcquire 方法, nonfairTryAcquire方法又在Sync类中实现
Sync.nonfairTryAcquire方法如下
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread(); //获取当前线程
int c = getState(); // 获取当前锁的计数器
if (c == 0) { // 计数器0,说明当前锁是空闲的
if (compareAndSetState(0, acquires)) { //比较并获取锁
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { // 当前线程就是已经获取锁的线程
int nextc = c + acquires; // 直接计数器上加 acquires, 传入的是1
if (nextc < 0) // 计数器数字异常
throw new Error("Maximum lock count exceeded");
setState(nextc); // 设置计数器的数值. 这里可以直接设置, 因为当前线程就是已经获取锁的线程, 可重入锁就是体现在这的
return true;
}
return false; // 获取锁失败
}
- 这个方法的作用, 就是看下锁的状态是否可以直接获取锁, 两种情况可以直接获取锁
- 锁是空闲状态, 没有线程获取锁
- 当前线程就是获取锁的线程, 直接计数器加值表示重复获取锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法,
这个也是AbstractQueuedSynchronizer 的方法. 这个方法才是真正的自旋阻塞获取锁的方法
/**
* 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)) { // tryAcquire 就是尝试获取一下锁
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- node.predecessor(); 就是获取当前线程的上一个线程
- 如果当前线程获取锁成功, 就把当前线程设置成队列的头部, 并且方法返回false
- 如果当前线程获取锁失败, 就进入shouldParkAfterFailedAcquire 方法, shouldParkAfterFailedAcquire内部会判断当前线程的上个线程的状态标记, 如果标记是<0(标识有问题) 就把上个线程移除队列, 如果标识是 SIGNAL就返回true, 如果是其他的就设置成SIGNAL 并且返回false. SIGNAL 标识线程阻塞中
- parkAndCheckInterrupt方法会判断当前线程是否应该中断,
- finally 中的方法 在正常获取到锁的时候回运行, cancelAcquire 中把当前线程和前面的线程都移除队列
- 其中addWaiter(Node.EXCLUSIVE) 方法就是把当前线程封装成Node, 并且把这个Node增加在队列的尾部
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); //这个方法内部进行循环 比较并设置 成线程队列的尾部. 如果队列还是空的, 就new一个新的Node设置在头部
return node;
}
到这里 就是加锁逻辑全部走完
解锁的逻辑
unlock解锁方法
public void unlock() {
sync.release(1);
}
release方法在是在AQS中实现的
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease 解锁方法
是在Sync中实现的
protected final boolean tryRelease(int releases) {
int c = getState() - releases; //当前锁计数器 减去一些
if (Thread.currentThread() != getExclusiveOwnerThread()) //如果当前线程不是获取锁的线程,直接抛出异常
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { //此次释放后, 计数器等于0.
free = true;
setExclusiveOwnerThread(null); // 设置队列中锁线程标记为null
}
setState(c); // 设置新的计数器
return free;
}
- 释放锁的逻辑很简单
unparkSuccessor方法
在释放锁成功后, 会进行这个方法. 这个方法会把后续的线程唤醒. LockSupport.unpark(s.thread); 就是唤醒线程方法
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.unpark(s.thread);
}
FairSync公平锁的逻辑
lock方法
加锁逻辑, 与NoFairSync区别是直接
final void lock() {
acquire(1);
}
acquire方法与非公平锁的方法一样
tryAcquire方法
这个方法与NonfairSync中tryAcquire方法有区别的, NonfairSync中的tryAcquire 是调用了父类Sync中的nonfairTryAcquire方法, 感觉这里有点奇怪
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;
}
- hasQueuedPredecessors方法中的逻辑很绕, hasQueuedPredecessors 判断当前线程在队列中的第二位, 是返回false, 否则返回true
- 是第二位, 则进行compareAndSetState 方法, 比较并设置, 如果锁没有线程获取就尝试获取. 获取成功就标记当前线程为获取锁线程
- 如果state 不是0 , 表示已经有线程获取锁了,
if (current == getExclusiveOwnerThread())
判断当前线程是否已经是获取锁的线程. - 如果最终还是没有获取锁成功, 就返回false
公平锁和非分公平锁的获取锁的区别
Lock方法中的区别
- 公平锁中lock 会直接进入acquire 方法, 会直接进入队列中获取锁
- 非公平锁, 会先尝试下判断当前线程是否已经获取锁, 获取锁计数器0 尝试获取下, 获取失败才会进入acquire 方法
tryAcquire方法中的区别, 这个方法才是真正的一次获取锁的方法,
- 公平锁在compareAndSetState之前会调用下hasQueuedPredecessors 方法, 判断下当前节点是否是第二节点. 是第二节点才会获取锁
- 非公平锁 没有hasQueuedPredecessors判断.
java的ReentrantLock类详解的更多相关文章
- java之StringBuffer类详解
StringBuffer 线程安全的可变字符序列. StringBuffer源码分析(JDK1.6): public final class StringBuffer extends Abstract ...
- java之AbstractStringBuilder类详解
目录 AbstractStringBuilder类 字段 构造器 方法 public abstract String toString() 扩充容量 void expandCapacity(in ...
- java之StringBuilder类详解
StringBuilder 非线程安全的可变字符序列 .该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍).如果可能,建议优先采用该类,因为在 ...
- java.lang.Thread类详解
java.lang.Thread类详解 一.前言 位于java.lang包下的Thread类是非常重要的线程类,它实现了Runnable接口,今天我们来学习一下Thread类,在学习Thread类之前 ...
- Java中dimension类详解
Java中dimension类详解 https://blog.csdn.net/hrw1234567890/article/details/81217788
- java之Matcher类详解
在JDK 1.4中,Java增加了对正则表达式的支持. java与正则相关的工具主要在java.util.regex包中:此包中主要有两个类:Pattern.Matcher. Matcher 声明: ...
- Java的String类详解
Java的String类 String类是除了Java的基本类型之外用的最多的类, 甚至用的比基本类型还多. 同样jdk中对Java类也有很多的优化 类的定义 public final class S ...
- Java Properties工具类详解
1.Java Properties工具类位于java.util.Properties,该工具类的使用极其简单方便.首先该类是继承自 Hashtable<Object,Object> 这就奠 ...
- Java中ArrayList类详解
1.什么是ArrayList ArrayList就是传说中的动态数组,用MSDN中的说法,就是Array的复杂版本,它提供了如下一些好处: 动态的增加和减少元素 实现了ICollection和ILis ...
随机推荐
- 如何重置Portal for ArcGIS、ArcGIS Server管理员密码
忘记管理员密码是ArcGIS系统管理员司空见惯的情况.每次为了找回站点管理员密码,用户经常要测试多次.有没有一种快捷的解决方案呢?答案是有的. 下面将分别介绍如何重置Portal for ArcGIS ...
- (11)打鸡儿教你Vue.js
表单 v-model 指令在表单控件元素上创建双向数据绑定 <div id="app"> <p>单个复选框:</p> <input typ ...
- 利用nc当作备用shell管理方案.
ssh 有时候真的就是连不上了,然后是没什么然后了呢. 或者手残改错配置然后重新sshd了. 所以这时候需要备用的远程管理工具.nc是最好的选择,一般服务器都是 内网的,如果跳板机也管理不了呢. 安装 ...
- CSP-S2019 快乐爆0
hhh 我爆0了 快乐 大家都比我强 hh 常规操作 本来就是个憨憨 回去复习文化课了 唉 干啥啥不行
- 模板 - 数学 - 数论 - Min_25筛
终于知道发明者的正确的名字了,是Min_25,这个筛法速度为亚线性的\(O(\frac{n^{\frac{3}{4}}}{\log x})\),用于求解具有下面性质的积性函数的前缀和: 在 \(p\) ...
- 数据结构Java版之邻接表实现图(十)
邻接表实现图,实际上是在一个数组里面存放链表,链表存放的是连接当前节点的其他节点. package mygraph; import java.util.ArrayList; import java.u ...
- 一段js MD5。加密 转换C#语法过程
A 帮忙把这段js脚本转换 c#语言. JS: function md5 (bit,sMessage) {debugger //var sMessage = this; function Rotate ...
- 小福bbs-冲刺日志(第二天)
[小福bbs-冲刺日志(第二天)] 这个作业属于哪个课程 班级链接 这个作业要求在哪里 作业要求的链接 团队名称 小福bbs 这个作业的目标 UI重构完成 作业的正文 小福bbs-冲刺日志(第二天) ...
- 下载GO的开源开发工具LITEIDE
下载GO的开源开发工具LITEIDE LITEIDE是免费且开源的GO IDE,支持WINDOWS, LINUX, MACOS https://sourceforge.net/projects/lit ...
- python:如何获取当前的日期和时间
# coding=utf-8 import datetime import time print ("格式参数:") print (" %a 星期几的简写") ...