ReentrantLock与Synchronized区别在于后者是JVM实现,前者是JDK实现,属于Java对象,使用的时候必须有明确的加锁(Lock)和解锁(Release)方法,否则可能会造成死锁。

先来查看ReentrantLock的继承关系(下图),实现了Lock和Serializable接口,表明ReentrantLock对象是可序列化的。

同时在ReentrantLock内部还定义了三个重要的内部类,Sync继承自抽象类AbstractQueuedSynchronizer(队列同步器)。其后又分别定义了它的两个子类公平锁FairSync和非公平锁NonfairSync。

    /**
* Base of synchronization control for this lock. Subclassed
* into fair and nonfair versions below. Uses AQS state to
* represent the number of holds on the lock.
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L; /**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
abstract void lock(); /**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == ) {
if (compareAndSetState(, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < ) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
} protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == ) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
} protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
} final ConditionObject newCondition() {
return new ConditionObject();
} // Methods relayed from outer class final Thread getOwner() {
return getState() == ? null : getExclusiveOwnerThread();
} final int getHoldCount() {
return isHeldExclusively() ? getState() : ;
} final boolean isLocked() {
return getState() != ;
} /**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(); // reset to unlocked state
}
} /**
* 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(, ))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire();
} protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
} /**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L; final void lock() {
acquire();
} /**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == ) {
if (!hasQueuedPredecessors() &&
compareAndSetState(, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < )
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}

然后先看一下ReentrantLock的构造函数:

    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();
}
ReentrantLock():无参构造器,默认的是非公平锁。
ReentrantLock(boolean):有参构造器,根据参数指定公平锁还是非公平锁。

从这里可以看出,ReentrantLock其实既可以是公平锁也可以是非公平锁,通过参数来进行自定义。

然后我们看一下加锁方法Lock:

    public void lock() {
sync.lock();
}

内部是调用了构造器中创建的Sync对象,由于默认的是非公平锁,因此我们先来看一下非公平锁的实现。

        final void lock() {
if (compareAndSetState(, ))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire();
}

从方法名compareAndSetState可以看出这是一个CAS操作,我们点进去查看源码,这是在AbstractQueuedSynchronized里面定义的一个方法

    protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

通过Unsafe对象来进行CAS操作。由于Unsafe里面定义的是Native方法,通过其他语言实现了对内存的直接操作,因此是保证了线程安全的。

然后我们再来看操作成功后的代码:setExclusiveOwnerThread(Thread.currentThread());

    protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}

这个方法的实现是在AbstractQueuedSynchronized的父类AbstractOwnableSynchronized中进行的,只是记录了当前拥有锁的线程。由于我们在if判断中已经获取到了锁,因此这一步也是线程安全的。由此,非公平锁获取结束。

然后我们再看看如果获取锁失败后的执行方法:acquire(1);获取锁失败,则说明现在已经有其他线程获取到了锁,并且正在执行代码块里面的内容。我们假设这个线程为B。

    public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

这个方法是被定义在AbstractQueuedSynchronized中,里面只有三行代码,但是需要注意,在这里的时候就可能会发生同步执行。

首先看最下面的如果条件成立执行的方法:selfInterrupt()

    static void selfInterrupt() {
Thread.currentThread().interrupt();
}

这个方法很简单,令当前线程中断。但需要注意的是,这个中断只是把线程里面的中断标志位改为true,并没有实际的对线程进行阻塞。线程阻塞已经在上面的两个判断条件里面完成了。

然后我们再来看下上面的判断条件:

首先是tryAcquire(arg),调用非公平锁的tryAcquire(int),里面又调用了Sync的nonfairTryAcquire(int)方法,通过判断当前的锁状态是否等于0,等于则表示没有线程获取锁(实际有可能是线程B已经执行完成并已经释放锁),再次尝试用CAS操作获取锁,获取成功则返回true,并且记录当前线程。如果获取失败,或者锁状态不等于0,则表示已经有线程获取到锁,此时会比较记录的线程是否为当前线程,如果是,则表示是当前线程重入(这里可以看出ReentrantLock是可重入锁),再令锁状态state加1,返回true,否则没有获取到锁返回false。

在这里我们可以看到tryAcquire()目的是再次判断当前锁是否是可获取状态(线程B已经执行完成并释放锁)以及是否是同一个线程的重入操作。获取锁成功或者是线程重入则返回true,lock方法就此结束。否则继续执行第二个条件判断。

    static final class NonfairSync extends Sync {

        protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
} abstract static class Sync extends AbstractQueuedSynchronizer {
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == ) {
if (compareAndSetState(, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < ) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}

再次获取锁失败后,会通过addWaiter()将当前线程添加到FIFO队列中。

在AQS(队列同步器)中通过Node内部类来制定一个双向链表,此链表采取的是先进先出(FIFO)策略。同时定义了一个头结点head和尾节点tail,都使用关键字volatile来保证多线程的可见性。

    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) { //判断当前尾节点是否等于null
node.prev = pred;
if (compareAndSetTail(pred, node)) { //尾节点不等于null,通过CAS操作将当前节点替换为链表尾节点。替换成功令当前节点作为前一个节点的next节点。替换失败则说明有其他线程正在操作,进入enq进行操作。
pred.next = node;
return node; //操作成功,返回当前节点。
}
}
enq(node); //自旋获取锁
return node;
} private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update); //这里是通过偏移量上值比较来进行值替换。
}

//进入这个方法有两种可能,一是当前链表没有初始化,等于null,二是当前线程与其他线程竞争添加线程到尾节点失败。
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;
}
}
}
}

新的线程被添加到队列里面后,再调用方法acquireQueue(Node,int);

这里可以简单的理解,线程自旋,如果当前线程的前一个节点是头节点(头结点代表获取到锁且正在执行的线程),说明下一个移出队列参与竞争锁的线程是当前线程,再次尝试获取锁,获取到了说明前一个节点已经执行完,令当前节点替换头节点,并返回中断标志位false。

获取锁失败说明上一个线程仍未执行完,或者锁被其他线程竞争到(新建的线程尚未添加到队列中,可以参与锁竞争),同时如果当前线程的上一个节点不是头节点(说明下一个移出队列竞争锁的线程不是当前线程),都会将线程节点的前一个节点的标志位设置为SIGNAL(表示下一个节点需要被unparking),然后令当前线程中断,暂停循环,等待唤醒。

    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; //获取锁成功返回false;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) //不会一直循环下去,因为会不断地消耗资源,适时会进入中断,等待被唤醒后才继续自旋。
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
} final Node predecessor() throws NullPointerException { //获取当前节点的前节点
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
} private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; //在这里Node的线程状态一共有5种情况:SIGNAL=-1,CANCELLED=1,CONDITION=-2,PROPAGATE=-3,以及默认值0
if (ws == Node.SIGNAL) //SIGNAL表示唤醒状态
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > ) { //大于0的只有CANCELLED情况,当前线程被取消执行,因此从队列中剔除
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > );
pred.next = node;
} else { //剩下的不论什么情况,都会利用CAS操作尝试将节点的waitStatus改为SINGAL,不论操作成功还是失败,都会返回false
/*
* 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;
} private static final boolean compareAndSetWaitStatus(Node node,
int expect,
int update) { //利用CAS操作修改节点的waitStatus值
return unsafe.compareAndSwapInt(node, waitStatusOffset,
expect, update);
} private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
} private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return; node.thread = null; // Skip cancelled predecessors
Node pred = node.prev;
while (pred.waitStatus > )
node.prev = pred = pred.prev; // predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
Node predNext = pred.next; // Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
node.waitStatus = Node.CANCELLED; // If we are the tail, remove ourselves.
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= )
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
} node.next = node; // help GC
}
}

由此,整个非公平锁的加锁过程结束,总结一下:

1.如果state位是0,则表示没有线程获取对象锁,通过CAS操作设置state位从0到1,尝试获取锁

2.获取锁成功,记录当前获取锁的线程。流程结束

3.获取失败,判断是否是已经获取了锁的线程再次获取(通过第二步里面记录的线程与当前线程判断是否相等),如果是,令state再加1,流程结束

4.如果不是,将线程添加到FIFO链表队列中,然后进行自旋。

5.自旋时会判断当前线程是否是head节点的next,如果是则再次尝试获取锁,获取到了后将头节点替换为当前节点,返回false。流程结束

6.自旋一定次数后仍未获取到锁,或当前线程节点不是下一个参与竞争锁的线程,则进入中断。等待被唤醒后继续自旋。

公平锁的Lock()方法:

        final void lock() {
acquire();
}
        protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == ) {
if (!hasQueuedPredecessors() && //公平锁与非公平锁的加锁区别
compareAndSetState(, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < )
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}

公平锁与非公平锁的加锁方法区别在于,tryAcquire(int)方法的不同。公平锁中要判断队列里第一个线程是否是当前线程,如果是,则允许它获取锁,如果不是,则不能获取。

下面看一下解锁方法:unlock()

    public void unlock() {
sync.release();
}

内部不分公平锁与非公平锁,一律调用AbstractQueuedSynchronized方法的release(int)。

    public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != )
unparkSuccessor(h);
return true;
}
return false;
}

先看第一行的判断:if(tryRelease(arg))

里面先判断了锁指向的线程与当前线程相等,不相等则抛出异常。

再令status减1,判断结果是否等于0。等于0说明可以释放锁,将锁指向的线程改为null,status改为0,返回true。

不等于0则说明仍未全部执行完重入的操作,令status自减一,返回false。

        protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == ) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}

判断为false则释放锁失败,判断为true则继续执行if里面内容。

if里面主要是判断了链表队列head里面有等待唤醒的其他线程节点,对他们进行一个唤醒。

    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 < )
compareAndSetWaitStatus(node, ws, ); //将waitStatus赋值为初始状态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 > ) { //下一个节点等于null或者被取消执行,从尾节点开始向前遍历,找到最头位置上的节点
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= )
s = t;
}
if (s != null) //唤醒队列中的下一个可执行的节点。
LockSupport.unpark(s.thread);
}

解锁流程总结:

1.锁指向的线程与当前线程必须是同一个线程。

2.锁标志位status必须已经减到0。

3.判断链表队列不等于null,并且头节点的waitStatus标志位不等于0,需要唤醒下一个节点。否则返回true,业务结束

4.唤醒下一个节点首先利用CAS操作将waitStatus的标志位改为0,然后再按队列顺序获取下一个节点。

5.如果获取的新节点等于null,或者waitStatus位等于1(表示已经被取消执行),则从尾节点向前遍历,直到遇见最前面的非null非当前线程节点的节点。

6.唤醒获取的新节点。业务结束

ReentrantLock源码的更多相关文章

  1. Java并发系列[5]----ReentrantLock源码分析

    在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可 ...

  2. Java并发编程笔记之ReentrantLock源码分析

    ReentrantLock是可重入的独占锁,同时只能有一个线程可以获取该锁,其他获取该锁的线程会被阻塞后放入该锁的AQS阻塞队列里面. 首先我们先看一下ReentrantLock的类图结构,如下图所示 ...

  3. Java并发编程-ReentrantLock源码分析

    一.前言 在分析了 AbstractQueuedSynchronier 源码后,接着分析ReentrantLock源码,其实在 AbstractQueuedSynchronizer 的分析中,已经提到 ...

  4. 第六章 ReentrantLock源码解析2--释放锁unlock()

    最常用的方式: int a = 12; //注意:通常情况下,这个会设置成一个类变量,比如说Segement中的段锁与copyOnWriteArrayList中的全局锁 final Reentrant ...

  5. java多线程---ReentrantLock源码分析

    ReentrantLock源码分析 基础知识复习 synchronized和lock的区别 synchronized是非公平锁,无法保证线程按照申请锁的顺序获得锁,而Lock锁提供了可选参数,可以配置 ...

  6. ReentrantLock源码分析--jdk1.8

    JDK1.8 ArrayList源码分析--jdk1.8LinkedList源码分析--jdk1.8HashMap源码分析--jdk1.8AQS源码分析--jdk1.8ReentrantLock源码分 ...

  7. 死磕 java同步系列之ReentrantLock源码解析(二)——条件锁

    问题 (1)条件锁是什么? (2)条件锁适用于什么场景? (3)条件锁的await()是在其它线程signal()的时候唤醒的吗? 简介 条件锁,是指在获取锁之后发现当前业务场景自己无法处理,而需要等 ...

  8. JUC AQS ReentrantLock源码分析

    警告⚠️:本文耗时很长,先做好心理准备,建议PC端浏览器浏览效果更佳. Java的内置锁一直都是备受争议的,在JDK1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6 ...

  9. java源码-ReentrantLock源码分析-1

    ReentrantLock 继承于lock是比较常用的独占锁,接下来我们来分析一下ReentrantLock源码以及接口设计: Sync是ReentrantLock的内部静态抽象类继承Abstract ...

  10. ReentrantLock 源码分析从入门到入土

    回答一个问题 在开始本篇文章的内容讲述前,先来回答我一个问题,为什么 JDK 提供一个 synchronized 关键字之后还要提供一个 Lock 锁,这不是多此一举吗?难道 JDK 设计人员都是沙雕 ...

随机推荐

  1. 学习STM32单片机,从菜鸟到牛人就是这样简单(配视频资料)

    我想说,为了学习单片机而去学习单片机的思路不对. 你问,如何系统地入门学习stm32? 本身就是一个错误的问题.假如你会使用8051 , 会写C语言,那么STM32本身并不需要刻意的学习. 你要考虑的 ...

  2. Google 内部代码是不支持异常(Excepton)的,C++ 异常的优劣之处有许多讨论(知乎上的讨论)

    最近 Google 开源了其内部多年使用的 C++ 代码库 Abseil 作为 C++ 标准库的补充,并会对其进行持续更新,本文对其进行简要介绍. 一句话概括,这个库的特点是用 C++ 11 的代码实 ...

  3. [vue开发记录]vue仿ios原生datepicker实现

    先上个效果图 现在只开发了年月,还在优化. 在网上看了一个纯原生js实现实现惯性滚动和回弹的文章  地址:https://www.cnblogs.com/ranyonsue/p/8119155.htm ...

  4. Spark入门到精通--(第八节)环境搭建(Hadoop搭建)

    上一节把Centos的集群免密码ssh登陆搭建完成,这一节主要讲一下Hadoop的环境搭建. Hadoop下载安装 下载官网的Hadoop 2.4.1的软件包.http://hadoop.apache ...

  5. python做数据驱动

    python代码如下: import unittestfrom openpyxl import load_workbookfrom openpyxl.styles import Fontfrom op ...

  6. .net中使用 道格拉斯-普特 抽希轨迹点

    Douglas一Peukcer算法由D.Douglas和T.Peueker于1973年提出,简称D一P算法,是目前公认的线状要素化简经典算法.现有的线化简算法中,有相当一部分都是在该算法基础上进行改进 ...

  7. 【托业】【怪兽】TEST04

    ❤ admit doing sth 承认做某事 ❤revelation n.揭露,揭示 ❤dazzling adj. 炫目的 ❤intentionally adv.刻意地 ❤metropolitan ...

  8. 给opencart产品页添加额外信息

    有时我们在开发opencart时需要给产品页添加一些额外的信息,第一种聪明的方法可以修改并调用已有字段,详细可以参考opencart3产品页调用upc/数量等信息:如果您的开发能力不错的话可以用第二种 ...

  9. 分享一段js,判断是否是在iPhone中的Safari浏览器打开的页面

    头部引用jquery包 将下面的一段js写在</body>的前面 <script type="text/javascript"> var ua = navi ...

  10. 深入了解webpack前,可以了解的小知识点。

    阅读前:文章大概是写,简单用过webpack,想继续深入了解webpack前需要了解的知识.但文章内容跟webpack使用关系并不大. 文章概要: Object.defineProperty call ...