说到可重入锁,先从AQS的ConditionObject说起,AQS的内部类ConditionObject是构建显示锁条件队列的基础。之前AQS的解析没有说这个内部类,这里和ReentrantLock一起说一下。

1、AQS的内部类ConditionObject

addConditionWaiter方法的代码:

这个方法的作用是将当前线程封装好放到条件队列。

 1     private Node addConditionWaiter() {
2 Node t = lastWaiter;
3 // If lastWaiter is cancelled, clean out.
4 if (t != null && t.waitStatus != Node.CONDITION) {//如果等待队列的最后一个是取消状态,就把遍历整个队列把所有取消状态的节点清除
5 unlinkCancelledWaiters();
6 t = lastWaiter;
7 }
8 Node node = new Node(Thread.currentThread(), Node.CONDITION);//以当前线程新建一个节点
9 if (t == null)//根据条件将节点放入队列
10 firstWaiter = node;
11 else
12 t.nextWaiter = node;
13 lastWaiter = node;
14 return node;//返回当前线程的节点
15 }

doSignal方法的代码:

这个方法的作用就是唤醒当前线程。

1         private void doSignal(Node first) {
2 do {
3 if ( (firstWaiter = first.nextWaiter) == null)
4 lastWaiter = null;
5 first.nextWaiter = null;
6 } while (!transferForSignal(first) &&//调用了transferForSignal方法
7 (first = firstWaiter) != null);
8 }

transferForSignal方法的代码:

 1     final boolean transferForSignal(Node node) {
2 /*
3 * If cannot change waitStatus, the node has been cancelled.
4 */
5 if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))//cas修改值,如果成功等待到了条件成立;如果失败节点的状态改为了CANCELLED,直接返回
6 return false;
7
8 /*
9 * Splice onto queue and try to set waitStatus of predecessor to
10 * indicate that thread is (probably) waiting. If cancelled or
11 * attempt to set waitStatus fails, wake up to resync (in which
12 * case the waitStatus can be transiently and harmlessly wrong).
13 */
14 Node p = enq(node);//节点入队 这里可能会有点晕,这个节点不是刚从队列里取出吗,怎么又入队了 其实是因为队列不是同一个,每个ConditionObject对象都有一个条件队列,队列里的节点等待的条件成立了会被添加到AQS的队列,这时节点(线程)才有资格等待获取资源
15 int ws = p.waitStatus;
16 if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))//唤醒线程
17 LockSupport.unpark(node.thread);
18 return true;
19 }

doSignalAll方法的代码:

这个方法的作用就是唤醒所有节点(线程)。

1         private void doSignalAll(Node first) {
2 lastWaiter = firstWaiter = null;
3 do {
4 Node next = first.nextWaiter;
5 first.nextWaiter = null;
6 transferForSignal(first);
7 first = next;
8 } while (first != null);
9 }

unlinkCancelledWaiters方法的代码:

这个方法被上面说的addConditionWaiter方法调用,将队列的取消状态的节点(线程)清除。

 1         private void unlinkCancelledWaiters() {
2 Node t = firstWaiter;
3 Node trail = null;
4 while (t != null) {
5 Node next = t.nextWaiter;
6 if (t.waitStatus != Node.CONDITION) {
7 t.nextWaiter = null;
8 if (trail == null)
9 firstWaiter = next;
10 else
11 trail.nextWaiter = next;
12 if (next == null)
13 lastWaiter = trail;
14 }
15 else
16 trail = t;
17 t = next;
18 }
19 }

doSignal方法的代码:

1         public final void signal() {//唤醒一个节点(线程)
2 if (!isHeldExclusively())
3 throw new IllegalMonitorStateException();
4 Node first = firstWaiter;
5 if (first != null)
6 doSignal(first);
7 }

doSignalAll方法的代码:

1         public final void signalAll() {//唤醒所有节点
2 if (!isHeldExclusively())
3 throw new IllegalMonitorStateException();
4 Node first = firstWaiter;
5 if (first != null)
6 doSignalAll(first);
7 }

awaitUninterruptibly方法的代码:

这个方法的作用就是将节点(线程)放入条件队列。

 1         public final void awaitUninterruptibly() {
2 Node node = addConditionWaiter();//放入条件队列
3 int savedState = fullyRelease(node);//释放资源
4 boolean interrupted = false;
5 while (!isOnSyncQueue(node)) {//当前节点是否在同步队列(在条件队列被唤醒后进入)
6 LockSupport.park(this);//如果不在,等待
7 if (Thread.interrupted())//检查是被中断还是被唤醒
8 interrupted = true;//如果是被中断设置interrupted,下面会设置中断标志
9 }
10 if (acquireQueued(node, savedState) || interrupted)//尝试获取资源 设置中断标志
11 selfInterrupt();
12 }

await方法的代码:

await方法和awaitUninterruptibly方法很相似,不同的地方是中断的处理。await方法的节点()线程如果是中断结束等待的,会根据当前中断的处理模式判断是抛出异常还是保留中断标志,不会再尝试获取资源。

 1         public final void await() throws InterruptedException {
2 if (Thread.interrupted())
3 throw new InterruptedException();
4 Node node = addConditionWaiter();
5 int savedState = fullyRelease(node);
6 int interruptMode = 0;
7 while (!isOnSyncQueue(node)) {
8 LockSupport.park(this);
9 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)//如果是中断,break
10 break;
11 }
12 if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
13 interruptMode = REINTERRUPT;
14 if (node.nextWaiter != null) // clean up if cancelled
15 unlinkCancelledWaiters();
16 if (interruptMode != 0)
17 reportInterruptAfterWait(interruptMode);//抛出异常或保留中断标志
18 }

ConditionObject的代码先说到这,下面说说ReentrantLock的代码。

2、ReentrantLock

ReentrantLock是独占模式的AQS,ReentrantLock有三个内部类,一个是抽象内部类。三个内部类为Sync、NonfairSync和FairSync。后面两个继承自前面一个。下面先说说这三个内部类。

Sync:

nonfairTryAcquire方法:

该方法的作用就是非公平地获取资源。

 1         final boolean nonfairTryAcquire(int acquires) {
2 final Thread current = Thread.currentThread();
3 int c = getState();
4 if (c == 0) {//如果没有线程占有资源
5 if (compareAndSetState(0, acquires)) {//如果cas获取资源成功
6 setExclusiveOwnerThread(current);//将当前线程设置为独占资源的线程
7 return true;
8 }
9 }
10 else if (current == getExclusiveOwnerThread()) {//如果当前线程是占有资源的线程
11 int nextc = c + acquires;//增加state ReentrantLock里acquires都传1 state为n说明当前线程重入的次数为n - 1
12 if (nextc < 0)//溢出
13 throw new Error("Maximum lock count exceeded");
14 setState(nextc);//设置state值
15 return true;
16 }
17 return false;
18 }

tryRelease方法:

该方法的作用就是释放资源(锁),如果资源值(state)为0,则说明调用unlock的次数和调用lock的次数一样,锁可以释放供其它线程获取。

 1         protected final boolean tryRelease(int releases) {
2 int c = getState() - releases;
3 if (Thread.currentThread() != getExclusiveOwnerThread())//如果当前线程不是独占占有资源的线程
4 throw new IllegalMonitorStateException();//抛出异常
5 boolean free = false;
6 if (c == 0) {//如果c等于0 资源全部释放(也就是释放了锁)
7 free = true;
8 setExclusiveOwnerThread(null);
9 }
10 setState(c);//设置state的值
11 return free;//返回是否释放了锁
12 }

newCondition方法:

该方法的作用就是创建新的条件队列。

1         final ConditionObject newCondition() {
2 return new ConditionObject();
3 }

NonfairSync:

lock方法:

非公平获取锁,每个线程获取锁时都会先cas试一下,如果成功了就直接获取了锁,不用再从入队出队开始。

1         final void lock() {
2 if (compareAndSetState(0, 1))
3 setExclusiveOwnerThread(Thread.currentThread());
4 else
5 acquire(1);//如果不成功还要排队继续
6 }

tryAcquire方法:

1         protected final boolean tryAcquire(int acquires) {
2 return nonfairTryAcquire(acquires);
3 }

FairSync:

lock方法:

公平获取锁,直接调用acquire,从入队出队开始。

        final void lock() {
acquire(1);
}

tryAcquire方法:

tryAcquire方法和nonfairTryAcquire方法类似,主要区别在于tryAcquire要求获取资源的节点(线程)必须是队首节点。

        protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {//如果没有前驱节点(线程)且cas设置state成功,当前节点(线程)独占占有锁
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;
}

说完了内部类,再看看几个提供的方法。

 1     public ReentrantLock() {
2 sync = new NonfairSync();
3 }
4
5 public ReentrantLock(boolean fair) {
6 sync = fair ? new FairSync() : new NonfairSync();
7 }
8
9 public void lock() {
10 sync.lock();
11 }
12
13 public void lockInterruptibly() throws InterruptedException {
14 sync.acquireInterruptibly(1);
15 }
16
17 public boolean tryLock() {
18 return sync.nonfairTryAcquire(1);
19 }
20
21 public Condition newCondition() {
22 return sync.newCondition();
23 }

会发现这些方法基本上都是直接调用上面或之前说的那些方法。

可重入锁ReentrantLock解析的更多相关文章

  1. synchronized关键字,Lock接口以及可重入锁ReentrantLock

    多线程环境下,必须考虑线程同步的问题,这是因为多个线程同时访问变量或者资源时会有线程争用,比如A线程读取了一个变量,B线程也读取了这个变量,然后他们同时对这个变量做了修改,写回到内存中,由于是同时做修 ...

  2. Java 重入锁 ReentrantLock 原理分析

    1.简介 可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似.所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻塞住,这样可避免死锁的产生 ...

  3. 轻松学习java可重入锁(ReentrantLock)的实现原理

    转载自https://blog.csdn.net/yanyan19880509/article/details/52345422,(做了一些补充) 前言 相信学过java的人都知道 synchroni ...

  4. java 可重入锁ReentrantLock的介绍

    一个小例子帮助理解(我们常用的synchronized也是可重入锁) 话说从前有一个村子,在这个村子中有一口水井,家家户户都需要到这口井里打水喝.由于井水有限,大家只能依次打水.为了实现家家有水喝,户 ...

  5. 轻松学习java可重入锁(ReentrantLock)的实现原理(转 图解)

    前言 相信学过java的人都知道 synchronized 这个关键词,也知道它用于控制多线程对并发资源的安全访问,兴许,你还用过Lock相关的功能,但你可能从来没有想过java中的锁底层的机制是怎么 ...

  6. 17_重入锁ReentrantLock

    [概述] 重入锁可以完全代替synchronized关键字. 与synchronized相比,重入锁ReentrantLock有着显示的操作过程,即开发人员必须手动指定何时加锁,何时释放锁,所以重入锁 ...

  7. Java 显示锁 之 重入锁 ReentrantLock(七)

    ReentrantLock 重入锁简介 重入锁 ReentrantLock,顾名思义,就是支持同一个线程对资源的重复加锁.另外,该锁还支持获取锁时的公平与非公平性的选择. 重入锁 ReentrantL ...

  8. Java并发(九):重入锁 ReentrantLock

    先做总结: 1.为什么要用ReentrantLock? (1)ReentrantLock与synchronized具有相同的功能和内存语义: (2)synchronized是重量级锁,性能不好.Ree ...

  9. Java并发包4--可重入锁ReentrantLock的实现原理

    前言 ReentrantLock是JUC提供的可重入锁的实现,用法上几乎等同于Synchronized,但是ReentrantLock在功能的丰富性上要比Synchronized要强大. 一.Reen ...

随机推荐

  1. uni-app开发经验分享十一: uniapp iOS云打包修改权限提示语

    打包提交appstore如果用到了如下权限需要修改提示语,详细描述使用这个权限的原因,如不修改提示语appstore审核可能会被拒绝.Apple的原则是,如果一个app想要申请用户同意某个隐私信息访问 ...

  2. 给定HDFS中某一个目录,输出该目录下的所有文件的读写权限、大小、创建时间、路径等信息,如果该文件是目录,则递归输出该目录下所有文件相关信息。

    1 import java.text.SimpleDateFormat; 2 import org.apache.hadoop.fs.*; 3 4 public class E_RecursiveRe ...

  3. 当中台遇上DDD,我们该如何设计微服务? - InfoQ https://www.infoq.cn/article/7QgXyp4Jh3-5Pk6LydWw

    当中台遇上DDD,我们该如何设计微服务? - InfoQ https://www.infoq.cn/article/7QgXyp4Jh3-5Pk6LydWw

  4. V2版的接口在V3版里面都能找到对应接口 数据结构

    开发文档 - 微信支付商户平台 https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/api.shtml 版本选择 关闭 V2版接口和V3版接口实际 ...

  5. 苹果 M1 芯片 OpenSSL 性能测试

    Apple M1(MacBook Air 2020) type 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes md2 0.00 0.00 0.00 ...

  6. webmvc 拦截器 允许跨域 跨域问题 sessionid不一样

    package cn.com.yitong.ares.filter; import java.io.IOException; import javax.servlet.Filter;import ja ...

  7. JavaScript基础知识-基本概念

    typeof操作符 typeof 操作符返回一个字符串,表示未经计算的操作数的类型. // 数值 typeof 37 === 'number'; typeof 3.14 === 'number'; t ...

  8. 在 WebAssembly 中实现回调的方式

    本文将介绍在 C++ 中实现 js 回调的几种方式. 在使用 wasm 的过程中, 避免不了要从 C++ 回调 js 的函数来实现异步交互. 官网文档 https://emscripten.org/d ...

  9. Java——继承,重载,重写三剑客

    About-继承 所有Java的类均是由java.lang.Object类继承而来的,所以Object是所有类的祖先类,而除了Object外,所有类必须有一个父类. 继承可以理解为一个对象从另一个对象 ...

  10. 网络编程(socket简介)

    socket简介 Python 提供了两个基本的 socket 模块. 第一个是 Socket,它提供了标准的 BSD Sockets API. 第二个是 SocketServer, 它提供了服务器中 ...