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判断.

最新文章

  1. Java基础学习(三)
  2. Handle机制的原理
  3. pace.js和NProgress.js两个加载进度插件的一点小总结
  4. javascript基础知识拾遗
  5. python 生成二维码
  6. easyui的datagrid组件,如何设置点击某行不会高亮该行的方式
  7. 三星Samsung 4.4.2该负责人制度,简化名单
  8. HDU 1043 八数码(八境界)
  9. Building Apps for Windows 10 on LattePanda–Jump Start
  10. 201521123001《Java程序设计》第9周学习总结
  11. oracle维护数据的完整性
  12. react 组件导出
  13. 牛客网 Python 编程输入规范
  14. 今天2.4寸tft触摸屏到手--刷屏驱动小结
  15. Ubuntu系统无法获得锁/var/lib/dpkg/lock - open (11: 资源暂时不可用)的解决方案
  16. [转]SendKeys.Send 方法
  17. TZOJ 2754 Watering Hole(最小生成树Kruskal)
  18. win8中常见问题排查
  19. 引发类型为“System.OutOfMemoryException”的异常
  20. 在Notepad++上搭建Windows下Shell运行环境

热门文章

  1. TFRecord 使用
  2. AtCoder Grand Contest 006 题解
  3. Django基础(2)-如何安装特定版本的Django项目
  4. eclipse 创建c/c++ 工程
  5. 基于腾讯云监控 API 的 Grafana App 插件开发
  6. CF1217题解
  7. nginx+keepalived高可用实战
  8. docker run 参数含义
  9. mysql 导出数据或结构
  10. CSS(1)