重入锁(ReentrantLock)是一种可重入无阻塞的同步机制。性能同synchronized接近(老版本jdk中性能很差)。

下面重点看下常用的lock()和unlock()方法的实现原理。

lock()

首先看下源代码:

    public void lock() {
sync.lock(); // 有公平同步和非公平同步两种机制
}

它的实现很简单,调用了一行sync的lock()方法,由于sync有两种实现方式:公平同步和非公平同步,默认是非公平同步,继续看代码:

final void lock() {
if (compareAndSetState(, )) // CAS机制,如果处于无锁状态,就直接锁定。lock锁中维护一个计数,大于0表示加锁了,值表示重入加锁次数
setExclusiveOwnerThread(Thread.currentThread()); // 设置锁被本线程持有,有什么用呢?用于可重入时检查使用
else    
acquire(); // 如果锁引用计数不是0,说明已经上锁,检查是否可以重入
}

继续看"acquire(1)"的实现:

    public final void acquire(int arg) {
if (!tryAcquire(arg) && // 判断是否可以重入,即持有锁的线程是否是当前线程,如果是就把锁引用计数加1,返回lock成功
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();
int c = getState(); // 锁引用计数值
if (c == 0) { // 为0,说明没有锁了
if (compareAndSetState(0, acquires)) { // 尝试加锁
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { // 持有锁的正是当前线程,那么就把锁引用计数加1,加锁成功
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

上面看到tryAcquire中会再次尝试加锁,或者如果持有锁的是当前线程,则把锁引用计数加1,返回加锁成功。

否则执行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; // 增加一个等待node带队尾
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 前面使用CAS方式设置失败,则进入enq,循环使用CAS方式把Node加入队尾
enq(node);
return node;
}
  // 把node加入队尾
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
// 加入队尾后,再执行acquireQueued(即acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
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)) { // 再次尝试加锁,防止有线程把锁释放了
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && // 检查是否可以把自己挂起,会设置一个状态值
parkAndCheckInterrupt()) // 把自己挂起
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

上面的代码,重点看下这两个方法:

shouldParkAfterFailedAcquire
 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; // 第一次进来 ws是0,是初始值
if (ws == Node.SIGNAL) // 是否已经设置为等待唤醒状态,如果是,就可以挂起了
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else { // 设置自己的状态为Node.SIGNAL,但本次不允许挂起。下次再进来的时候,就可以返回true,并可以挂起了
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
parkAndCheckInterrupt
     private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 挂起自己
return Thread.interrupted();
}
再看下park的实现:
     public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker); // 设置要挂起的线程,和挂起使用的对象
unsafe.park(false, 0L); // 挂起,jdk内置的挂起方法
setBlocker(t, null); // 唤醒后,取消挂起的对象
}

再看下setBlocker:

     private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
unsafe.putObject(t, parkBlockerOffset, arg);
} // 设置了阻塞线程和使用的阻塞对象
 
 

从上面代码可以看到,lock的主要流程有:

1. 检查锁引用计数,如果为0,表示可以锁定,就使用CAS方式把当前Thread对象设置为持有锁的对象,并把锁引用计数加1.

2. 检查锁引用计数,如果大于0,需要:

    i. 检查当前持有锁的线程对象是否和本线程是同一个,如果是就对锁引用计数加1,加锁成功,这里体现了"可重入"特性。

    ii. 否则,创建一个等待的node对象并加入到等待链接的队尾,然后调用系统的unsafe.park方法,把自己挂起。

 Unlock

unlock就比较简单了,下面看下代码:

    public void unlock() {
sync.release(1); // 解锁
} public final boolean release(int arg) {
if (tryRelease(arg)) { // 解锁
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 解锁成功后,唤醒其他的等待线程
return true;
}
return false;
}

这里面重点有两个地方,一个解锁,另外一个唤醒其他等待的线程。

先看解锁:

 // 索引用计数减releases,实现了重入
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { // 如果锁引用计数为0,说明是无锁状态了,需要把持有锁的线程变量置为空,并返回true
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}

再看唤醒其他线程代码:

 // 当前面方法返回true,说明当前处于无锁状态了,这时候就可以唤醒其他的等待线程了
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0); /*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
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); // 唤醒线程
}

相对来说,unlock就简单许多了,两步:1.解锁,减引用计数。2.唤醒其他线程。

ReentrantLock的原理解析的更多相关文章

  1. java线程池原理解析

    五一假期大雄看了一本<java并发编程艺术>,了解了线程池的基本工作流程,竟然发现线程池工作原理和互联网公司运作模式十分相似. 线程池处理流程 原理解析 互联网公司与线程池的关系 这里用一 ...

  2. Java并发包JUC核心原理解析

    CS-LogN思维导图:记录CS基础 面试题 开源地址:https://github.com/FISHers6/CS-LogN JUC 分类 线程管理 线程池相关类 Executor.Executor ...

  3. 深入浅出ReentrantLock源码解析

    ReentrantLock不但是可重入锁,而且还是公平或非公平锁,在工作中会经常使用到,将自己对这两种锁的理解记录下来,希望对大家有帮助. 前提条件 在理解ReentrantLock时需要具备一些基本 ...

  4. Java并发之ReentrantLock源码解析(四)

    Condition 在上一章中,我们大概了解了Condition的使用,下面我们来看看Condition再juc的实现.juc下Condition本质上是一个接口,它只定义了这个接口的使用方式,具体的 ...

  5. 从零开始实现lmax-Disruptor队列(二)多消费者、消费者组间消费依赖原理解析

    MyDisruptor V2版本介绍 在v1版本的MyDisruptor实现单生产者.单消费者功能后.按照计划,v2版本的MyDisruptor需要支持多消费者和允许设置消费者组间的依赖关系. 由于该 ...

  6. jdk线程池ThreadPoolExecutor工作原理解析(自己动手实现线程池)(一)

    jdk线程池ThreadPoolExecutor工作原理解析(自己动手实现线程池)(一) 线程池介绍 在日常开发中经常会遇到需要使用其它线程将大量任务异步处理的场景(异步化以及提升系统的吞吐量),而在 ...

  7. [原][Docker]特性与原理解析

    Docker特性与原理解析 文章假设你已经熟悉了Docker的基本命令和基本知识 首先看看Docker提供了哪些特性: 交互式Shell:Docker可以分配一个虚拟终端并关联到任何容器的标准输入上, ...

  8. 【算法】(查找你附近的人) GeoHash核心原理解析及代码实现

    本文地址 原文地址 分享提纲: 0. 引子 1. 感性认识GeoHash 2. GeoHash算法的步骤 3. GeoHash Base32编码长度与精度 4. GeoHash算法 5. 使用注意点( ...

  9. Web APi之过滤器执行过程原理解析【二】(十一)

    前言 上一节我们详细讲解了过滤器的创建过程以及粗略的介绍了五种过滤器,用此五种过滤器对实现对执行Action方法各个时期的拦截非常重要.这一节我们简单将讲述在Action方法上.控制器上.全局上以及授 ...

随机推荐

  1. [JVM] - 继10进制的java.lang.Object查看之后

    cmd清除命令:cls 之后查阅了其它博客,发现这位大神同样也在做JVM,并且我很希望用它的10进制转16进制类来测试一下该解析的10进制是否对应着Object的16进制呢? 这位大神的10进制转16 ...

  2. Java Session 会话技术

    什么是Java Session? 大图:http://images2017.cnblogs.com/blog/1030087/201712/1030087-20171223235950881-1310 ...

  3. C指针 的一些练习

    注:此篇是我使用指针敲的一些题目的集成,有一些代码是重复的(挠头).这样做的目的是进行前后的一些比较和收获一些心得(?). 关于上一次我上台的题目: 题目:输入十个整数,进行排序. 做法1:(传递指针 ...

  4. zeptojs库解读3之ajax模块

    对于ajax,三步骤,第一,创建xhr对象:第二,发送请求:第三,处理响应. 但在编写过程中,实际中会碰到以下问题, 1.超时 2.跨域 3.后退 解决方法: 1.超时 设置定时器,规定的时间内未返回 ...

  5. URAL 1658 Sum of Digits

    URAL 1658 思路: dp+记录路径 状态:dp[i][j]表示s1为i,s2为j的最小位数 初始状态:dp[0][0]=0 状态转移:dp[i][j]=min(dp[i-k][j-k*k]+1 ...

  6. 阿里官方Java代码规范标准

    阿里官方Java代码规范标准<阿里巴巴Java开发手册 终极版 v1.3.0>下载 https://www.cnblogs.com/han-1034683568/p/7680354.htm ...

  7. Typekit在线字库及使用方法

    一.如果设计中使用了非标准的字体,你该如何去实现? 所谓的标准字体是多数机器上都会有的,或者即使没有也可以由默认字体替代的字体. 方法: 用图片代替 web fonts在线字库,如Google Web ...

  8. 20170711xlVBA批量制图一例

    Public Sub GatherDataPicker() Application.ScreenUpdating = False Application.DisplayAlerts = False A ...

  9. 20170706xlVBA根据工资汇总表生成个人工资条

    Sub NextSeven20170706001() Application.ScreenUpdating = False Application.DisplayAlerts = False Appl ...

  10. Andorid 之日历控件,可左右滑动,包含公历,农历,节假日等

    公司项目需要日历这个功能,经过查阅资料写了个demo,包含公历,农历,节假日等,还可左右滑动. 效果图: 代码: public class MainActivity extends AppCompat ...