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. Exp2 后门原理与实践 20164320 王浩

    一.实践基本内容 1.实践目标 (1)使用netcat获取主机操作Shell,cron启动 (2)使用socat获取主机操作Shell, 任务计划启动 (3)使用MSF meterpreter(或其他 ...

  2. C++ 第二次实验

    实验内容: 1.函数重载编程练习 编写重载函数add(),实现对int型,double型,Complex型数据的加法.在main()函数中定义不同类型 数据,调用测试. #include <io ...

  3. Get WMS Static GoodLocation By Dynamic SQL

    Dynamic SQL Store Procedure: Note: use variable,you need convert varchar and as a variable,not direc ...

  4. Jenkins + Gerrit + Git

    参考:https://blog.csdn.net/mr_raptor/article/details/76223233 https://www.cnblogs.com/kevingrace/p/565 ...

  5. Epplus DataTable一次性导出

    public void Export() { string fileName = ""; if (string.IsNullOrEmpty(fileName) == true) { ...

  6. 6#day2总结

    一次小小的总结https://github.com/DuGuQiuBai/Java/blob/master/day02/day02%E6%80%BB%E7%BB%93.txt 1:常量(掌握) (1) ...

  7. sersync客户端搭建及配置

    首先需要自行下载sersync包,地址如下: 谷歌项目地址:https://code.google.com/archive/p/sersync/ 64位下载地址:https://storage.goo ...

  8. 虚拟机设置固定ip可以使shell远程连接到服务器

    配置vim /etc/sysconfig/network-scripts/ifcfg-ens33 IPADDR = 你的本机ip 192.168.1. 的范围内 NETMASK = 255.255.2 ...

  9. 大兄dei,早点看清this吧

    说道this,可以说是前端中很重要的问题之一了,也是面试或者笔试常考的问题.所以还是早点看清this吧,大兄dei. this是什么?为什么要存在? this关键字是js中最最复杂的机制之一.他被自动 ...

  10. Excel控制AutoCad进行坐标标注

    做过工程测绘,平面设计,使用过Autocad制图的朋友们,都经常要在CAD上标注点或者线的坐标,CAD自身的标注功能,并不能同时标注X和Y坐标,,要同时标注X和Y坐标,可以使用南方CASS软件,或者一 ...