ReentrantLock的功能是实现代码段的并发访问控制,也就是通常意义上所说的锁,java中实现锁有两种方式,一种是本文所提的ReentrantLock,另一种是synchronized。ReentrantLock相比synchronized 使用可以更灵活,这次就来看看ReentrantLock的内部实现。

我们首先看下ReentrantLock的锁是如何实现的

其实就一行代码 ,看起来很简单,那么这里的sync是什么呢?

这是ReentrantLock内部的一个抽象类,继承了AbstractQueuedSynchronizer(AQS),ReentrantLock所有的功能都和这个类有关

用过ReentrantLock的人都知道,ReentrantLock是分为公平锁和非公平锁,这在ReentrantLock内部是两种实现

公平锁:每个线程抢占锁的顺序为先后调用lock方法的顺序依次获取锁。

非公平锁:每个线程抢占锁的顺序不定,谁运气好,谁就获取到锁,和调用lock方法的先后顺序无关。

首先看下公平锁的内部实现

公平锁 加锁:


调用到了AQS的acquire方法:

再看下获取锁的代码

原来这里子类重写了

 protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取状态位
int c = getState();
//0,锁还没被拿走
if (c == 0) {
//如果队列中没有其他线程 说明没有线程正在占有锁
if (!hasQueuedPredecessors() &&
//修改状态为1
compareAndSetState(0, acquires)) {
//如果通过CAS操作将状态为更新成功则代表当前线程获取锁,
//因此,将当前线程设置到AQS的一个变量中,说明这个线程拿走了锁。
setExclusiveOwnerThread(current);
return true;
}
}
//锁被拿走了,由于ReentrantLock是可重入锁,所以判断下持有锁的是否是同一个线程
else if (current == getExclusiveOwnerThread()) {
//如果是的话累加在state字段上就可以了
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
} protected final int getState() {
return state;
}

再回到acquire方法中

我们看下addWaiter方法

 private Node addWaiter(Node mode) {
//用当前线程构造一个node,mode是一个表示Node类型的字段,
//仅仅表示这个节点是独占的,还是共享的
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的死循环,知道插入成功
enq(node);
return node;
}

将线程的节点添加队里中后,还需要做一件事:将当前线程挂起!这个事,由acquireQueued来做。

 final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//如果当前的前一个节点是head说明他是队列中第一个“有效的”节点,因此尝试获取,这个方法子类重写了。
if (p == head && tryAcquire(arg)) {
setHead(node);//获取成功之后设为将该节点头结点
p.next = null; // help GC 将原先的头结点的联系去除 这样原先头结点就可以被GC
failed = false;
return interrupted;
}
////否则,检查前一个节点的状态为,看当前获取锁失败的线程是否需要挂起。
if (shouldParkAfterFailedAcquire(p, node) &&
//如果需要,借助JUC包下的LockSopport类的静态方法Park挂起当前线程。直到被唤醒。
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
 private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}

这里需要在介绍下AQS中的Node节点的状态

黄色节点是默认head节点,默认是一个空节点,可以理解成代表当前持有锁的线程,每当有线程竞争失败,都是插入到队列的尾节点,tail节点始终指向队列中的最后一个元素。默认只有当前节点的pre节点是头结点才能去acquire

/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3; /**
* Status field, taking on only the values:
* SIGNAL: The successor of this node is (or will soon be)
* blocked (via park), so the current node must
* unpark its successor when it releases or
* cancels. To avoid races, acquire methods must
* first indicate they need a signal,
* then retry the atomic acquire, and then,
* on failure, block.
* CANCELLED: This node is cancelled due to timeout or interrupt.
* Nodes never leave this state. In particular,
* a thread with cancelled node never again blocks.
* CONDITION: This node is currently on a condition queue.
* It will not be used as a sync queue node
* until transferred, at which time the status
* will be set to 0. (Use of this value here has
* nothing to do with the other uses of the
* field, but simplifies mechanics.)
* PROPAGATE: A releaseShared should be propagated to other
* nodes. This is set (for head node only) in
* doReleaseShared to ensure propagation
* continues, even if other operations have
* since intervened.
* 0: None of the above
*
* The values are arranged numerically to simplify use.
* Non-negative values mean that a node doesn't need to
* signal. So, most code doesn't need to check for particular
* values, just for sign.
*
* The field is initialized to 0 for normal sync nodes, and
* CONDITION for condition nodes. It is modified using CAS
* (or when possible, unconditional volatile writes).
*/
volatile int waitStatus;
每个节点中, 除了存储了当前线程,前后节点的引用以外,还有一个waitStatus变量,用于描述节点当前的状态。多线程并发执行时,队列中会有多个节点存在,这个waitStatus其实代表对应线程的状态:有的线程可能获取锁因为某些原因放弃竞争;有的线程在等待满足条件,满足之后才能执行等等。一共有4中状态:

CANCELLED 取消状态

SIGNAL 等待触发状态

CONDITION 等待条件状态

PROPAGATE 状态需要向后传播

到此为止,一个线程对于锁的一次竞争才告于段落,结果有两种,要么成功获取到锁(不用进入到AQS队列中),要么,获取失败,被挂起,等待下次唤醒后继续循环尝试获取锁,值得注意的是,AQS的队列为FIFO队列,所以,每次被CPU假唤醒,且当前线程不是出在头节点的位置,也是会被挂起的

非公平锁 加锁:


非公平锁相对于公平锁,只有加锁的环节是不同的

非公平锁首先先去尝试修改AQS状态,如果成功了,当前线程就持有了锁,失败了,再像公平锁一样,进入队列,静静的等待

锁释放:


还是一行代码,再看看sync的内部实现

代码很直观明了,我们再看下是如何唤醒头结点的

至此一个完整的流程就结束了。我们再来总结下整个流程

以公平锁为例

加锁:

    1.尝试获取锁,判断state是否是0,是0的话CAS操作把state改为1,获取到锁
2.state不是0,判断下是否存在重入锁的情况
3.如果1和2都失败了,那就要把节点插入到链表中
4.再把这个线程挂起,挂起的时候还会尝试一次1和2的操作

释放锁:

    1.释放锁
2.唤醒AQS节点

ReentrantLock 详解的更多相关文章

  1. Lock的实现之ReentrantLock详解

    摘要 Lock在硬件层面依赖CPU指令,完全由Java代码完成,底层利用LockSupport类和Unsafe类进行操作: 虽然锁有很多实现,但是都依赖AbstractQueuedSynchroniz ...

  2. java之ReentrantLock详解

    前言 如果一个代码块被synchronized修饰了,当一个线程获取了相应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的释放,现在有这么一种情况,这个获取锁的线程由于要等待IO或者其他原 ...

  3. ReentrantLock详解 以及与synchronized的区别

    ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁 ReentrantLock lock = new ReentrantLock(tr ...

  4. Java并发之ReentrantLock详解

    一.入题 ReentrantLock是Java并发包中互斥锁,它有公平锁和非公平锁两种实现方式,以lock()为例,其使用方式为: ReentrantLock takeLock = new Reent ...

  5. ReentrantLock详解

    ReentrantLock概述 ReentrantLock是Lock接口的实现类,可以手动的对某一段进行加锁.ReentrantLock可重入锁,具有可重入性,并且支持可中断锁.其内部对锁的控制有两种 ...

  6. 图解AQS原理之ReentrantLock详解-非公平锁

    概述 并发编程中,ReentrantLock的使用是比较多的,包括之前讲的LinkedBlockingQueue和ArrayBlockQueue的内部都是使用的ReentrantLock,谈到它又不能 ...

  7. 从ReentrantLock详解AQS原理源码解析

    数据结构 java.util.concurrent.locks.AbstractQueuedSynchronizer类中存在如下数据结构. // 链表结点 static final class Nod ...

  8. 最强Java并发编程详解:知识点梳理,BAT面试题等

    本文原创更多内容可以参考: Java 全栈知识体系.如需转载请说明原处. 知识体系系统性梳理 Java 并发之基础 A. Java进阶 - Java 并发之基础:首先全局的了解并发的知识体系,同时了解 ...

  9. java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock

    原文:java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock 锁 锁是用来控制多个线程访问共享资源的方式,java中可以使用synch ...

随机推荐

  1. python3 tkinter添加图片和文本

    在前面一篇文章基础上,使用tkinter添加图片和文本.在开始之前,我们需要安装Pillow图片库. 一.Pillow的安装 1.方法一:需要下载exe文件,根据下面图片下载和安装       下载完 ...

  2. angular点击查看更多(简单demo)

    今天来跟大家分享一个小的demo,一般网页浏览到底部的时候会有一个点击加载更多的按钮,之前一直纠结怎么写这个,今天学习angular时发现可以用组件来实现这么一个小的效果,大家有兴趣的话可以看一下. ...

  3. spring InitializingBean和DisposableBean init-method 和destroy-method @PostConstruct @PreDestroy

    对于初始化函数: @PostConstruct 注解的方法 InitializingBean接口定义的回调afterPropertiesSet() Bean配置中自定义的初始化函数 对于析构则与上相同 ...

  4. 基于ajax提交数据

    昨日回顾: 1 inclusion_tag -干什么用的?生成html的片段(动态,传参数,传数据) -app下新建一个模块,templatetags -创建一个py文件(mytag.py) -fro ...

  5. STM32-跑马灯实验

    实验环境: STM32开发板 Keil uVision4 FlyMcu.exe 一. 建立文件夹 1.建立跟文件夹"BASE"(名称任意),再在其下建立四个子文件夹 2. 在&qu ...

  6. Java 8 特性

    1.简介 毫无疑问,Java 8是自Java  5(2004年)发布以来Java语言最大的一次版本升级,Java 8带来了很多的新特性,比如编译器.类库.开发工具和JVM(Java虚拟机).在这篇教程 ...

  7. 我的C#跨平台之旅(五):使用IoC之依赖注入实现

    引入NuGet包:Unity 实现接口:IDependencyResolver 在启动类中注入依赖的类: 注意:左框中的内容为接口或抽象类,右框中为实际要注入的类. 修改控制类,使用构造方法注入类: ...

  8. 简析 __init__、__new__、__call__ 方法

    简析 __init__.__new__.__call__ 方法 任何事物都有一个从创建,被使用,再到消亡的过程,在程序语言面向对象编程模型中,对象也有相似的命运:创建.初始化.使 用.垃圾回收,不同的 ...

  9. Python基础知识当中容易混淆的几个知识点

    在Python的基础知识当中,对于类实现可迭代功能有了一种新的方式,而这种方式则有别于我们学.NET等其他高级语言. 在Python当中,目前常用的有两种方式来实现这种迭代器的返回:__iter__ ...

  10. 二叉树遍历之三(Moriis traversal)

     二叉树的Morris traversal是个很值得学习的算法,也是此系列重点想要记叙的一个算法.Morris traversal的一个亮点在于它是O(1)空间复杂度的.前面的递归和迭代都是需要O(n ...