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方法

加锁方法, 方法内有两个分支逻辑,

  1. 先判断是否是无锁状态并尝试加锁compareAndSetState , 如果无锁且加锁成功则把当前线程加入AQS队列中setExclusiveOwnerThread.
  2. 尝试获取锁没有成功, 就调用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 &lt; 0) compareAndSetWaitStatus(node, ws, 0); // 获取当前节点的后继节点,如果满足状态,那么进行唤醒操作 // 如果没有满足状态,从尾部开始找寻符合要求的节点并将其唤醒 Node s = node.next; if (s == null || s.waitStatus &gt; 0) {
s = null;
for (Node t = tail; t != null &amp;&amp; t != node; t = t.prev)
if (t.waitStatus &lt;= 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方法中的区别

  1. 公平锁中lock 会直接进入acquire 方法, 会直接进入队列中获取锁
  2. 非公平锁, 会先尝试下判断当前线程是否已经获取锁, 获取锁计数器0 尝试获取下, 获取失败才会进入acquire 方法

tryAcquire方法中的区别, 这个方法才是真正的一次获取锁的方法,

  1. 公平锁在compareAndSetState之前会调用下hasQueuedPredecessors 方法, 判断下当前节点是否是第二节点. 是第二节点才会获取锁
  2. 非公平锁 没有hasQueuedPredecessors判断.

java的ReentrantLock类详解的更多相关文章

  1. java之StringBuffer类详解

    StringBuffer 线程安全的可变字符序列. StringBuffer源码分析(JDK1.6): public final class StringBuffer extends Abstract ...

  2. java之AbstractStringBuilder类详解

    目录 AbstractStringBuilder类 字段 构造器 方法   public abstract String toString() 扩充容量 void  expandCapacity(in ...

  3. java之StringBuilder类详解

    StringBuilder 非线程安全的可变字符序列 .该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍).如果可能,建议优先采用该类,因为在 ...

  4. java.lang.Thread类详解

    java.lang.Thread类详解 一.前言 位于java.lang包下的Thread类是非常重要的线程类,它实现了Runnable接口,今天我们来学习一下Thread类,在学习Thread类之前 ...

  5. Java中dimension类详解

    Java中dimension类详解 https://blog.csdn.net/hrw1234567890/article/details/81217788

  6. java之Matcher类详解

    在JDK 1.4中,Java增加了对正则表达式的支持. java与正则相关的工具主要在java.util.regex包中:此包中主要有两个类:Pattern.Matcher. Matcher  声明: ...

  7. Java的String类详解

    Java的String类 String类是除了Java的基本类型之外用的最多的类, 甚至用的比基本类型还多. 同样jdk中对Java类也有很多的优化 类的定义 public final class S ...

  8. Java Properties工具类详解

    1.Java Properties工具类位于java.util.Properties,该工具类的使用极其简单方便.首先该类是继承自 Hashtable<Object,Object> 这就奠 ...

  9. Java中ArrayList类详解

    1.什么是ArrayList ArrayList就是传说中的动态数组,用MSDN中的说法,就是Array的复杂版本,它提供了如下一些好处: 动态的增加和减少元素 实现了ICollection和ILis ...

随机推荐

  1. Loadrunner录制+运行+结果-【飞机订票系统实战】

    目录结构: 一.LoadRunner实现订票系统脚本录制 二.Loadrunner实现订票系统IP欺骗(此处可以不设置) 三.Loadrunner运行录制的脚本 四.Load generator配置 ...

  2. 深入浅出MYSQL数据库—思维导图[附下载链接]

    源文件下载地址:https://github.com/JluTiger/schoolRecruit2020

  3. Compiling OpenCV: VTK Not Found on Ubuntu 16.04 LTS

    When installing OpenCV: /usr/bin/vtk not found libvtkRenderingPythonTkWidgets.so not found /usr/bin/ ...

  4. [C++] typeid关键字使用方法

    typeid 关键字的作用就是获取一个表达式是类型,返回表达式的类型 表达式可以是类型名称.变量名.数字.字符串.指针.结构体等 #include <iostream> using nam ...

  5. CI框架常用函数(AR数据库操作的常用函数)

    用户手册地址:http://codeigniter.org.cn/user_guide/index.html 1.查询表记录$this->db->select(); //选择查询的字段$t ...

  6. (基因功能 & 基因表达调控)研究方案

    做了好久的RNA-seq分析,基因表达也在口头溜了几年了,但似乎老是浮在表面. 对一件事的了解程度决定了你的思维深度,只想做技工就不用想太多,想做大师就一定要刨根问底. 老是说基因表达,那么什么是基因 ...

  7. 天池移动推荐算法赛--https://github.com/PnYuan/Tianchi-BigData

    参考: https://blog.csdn.net/Snoopy_Yuan/article/details/75808006

  8. 网络分裂 redis 集群

    REDIS cluster-tutorial -- Redis中文资料站 -- Redis中国用户组(CRUG) http://www.redis.cn/topics/cluster-tutorial ...

  9. TextView: android:ellipsize="marquee" 跑马灯效果无效的问题

    今天练习的时候想实现一个文字的跑马灯效果,本来想自己手动实现的,不过突然想起来android里的TextView属性似乎自带了这个效果,叫: android:ellipsize ,平时都是把它的属性值 ...

  10. Vue实现图片预加载

    <script>export default { data () { return { count: 0, } }, mounted: function() { this.preload( ...