1.引子

"ReentrantLock"单词中的“Reentrant”就是“重入”的意思,正如其名,ReentrantLock是一个支持重入的排他锁,即同一个线程中可以多次获得同步状态,常表现为lock()方法的嵌套使用(类似于synchronized代码类嵌套),而在AQS类注释的使用说明中的Mutex是一个不可重入的锁,只要一个线程获得了同步状态,再次tryAcquire(int)返回false。

另外ReentrantLock还支持公平锁和非公平锁的的选择,公平锁是指等待时间长的线程优先获取锁,非公平锁则对所有线程一视同仁;synchronized关键字只支持非同步锁,这是由JVM的本地C++代码决定的。

公平锁虽能解决某些线程长久等待,减少“饥饿”的发生概率,但公平锁没有非公平锁的效率高,因为它要频繁地进行线程上下文切换,一般情况下使用非公平锁。ReentrantLock可以通过设置构造方法的参数来决定使用公平锁或非公平锁,其默认的无参构造方法创建的是一个非公平锁。

ReentrantLock与synchronized的区别对比在以前的帖子Lock接口简介中已经说明过了,这里不再赘述。

2.类结构

由类结构图可以看出,ReentrantLock类中有Sync、FairSync、NofairSync这三个静态内部类。Sync是一个继承于AQS的一个抽象类(AQS相关的内容在之前的帖子),它表示公平锁与非公平锁的通用或共同之处的抽象。FairSync 和NofairSync都继承自Sync,分别表示公平锁、非公平锁.它们两者的类定义差别很小,只有尝试获取锁的方法不同,FairSync使用自已定义的tryAcquire(int),而NotFairSync将tryAcquire(int)委托给父类Sync的nonfairTryAcquire(int)方法实现,而两者尝试释放锁的方法都是继承父类Sync的tryRelease(int)

ReentrantLock的构造方法

ReentrantLock有一个Sync类型的成员变量sync,这个成员变量在构造方法中被实例化。无参的构造方法,将sync实例化为一个NonfairSync对象,此时的ReentrantLock表示一个非公平锁。带一个布尔型参数的构造方法,根据参数就会创建相应的公平锁/非公平锁。

    private final Sync sync;
public ReentrantLock() {
sync = new NonfairSync();
} public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
    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 == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // 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 == 0) {
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() == 0 ? null : getExclusiveOwnerThread();
} final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
} final boolean isLocked() {
return getState() != 0;
} /**
* 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(0); // reset to unlocked state
}
}

Sync源代码

Sync的一些其他方法

        //当前线程是独占线程
protected final boolean isHeldExclusively() { return getExclusiveOwnerThread() == Thread.currentThread();
}
//给当前锁绑定一个新的条件
final ConditionObject newCondition() {
return new ConditionObject();
} //获得锁的线程
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
//锁被当前线程获取重复获取的次数
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
//锁否已经被某一线程获取到了
final boolean isLocked() {
return getState() != 0;
} /**
* 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(0); // reset to unlocked state
}

3.可重入的实现机制

锁的可重入是指,某线程在获取到锁之后还能获取到此锁而不会被此锁给阻塞。要保证这点要解决这两个方面的问题:

1) 线程再次获取锁。当要确定当前线程是否已经获取到了锁,如果是,那么再次获取锁也必须是成功的。代码的实现:每次获取锁将AQS的state属性自增,state表示锁被线程获取到的次数。

2) 锁释放。一线程获取了n次锁,那么也需要经过n次释放,锁才能完成审美观点释放,其他线程才能获取到此锁。代码实现思路:每次释放锁让AQS的state属性自减,当state为0时,表明锁被完全释放了。

非公平锁是默认实现,这里以非公平锁为例。

非公平锁获取锁lock()调用的底层方法nonfairTryAcquire(int)

        /**
* 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 == 0) {//没有任何线程获取同步状态
if (compareAndSetState(0, acquires)) {//cas成功,当前线程获取同步状态成功
setExclusiveOwnerThread(current);//设置当前线程为锁的独占线程
return true;
}
}
/**
*当前线程是独占线程,即当前线程之前已经获取到了同步状态.
* 进入重入处理
*/
else if (current == getExclusiveOwnerThread()) {
/**
* nonfairTryAcquire(int)方法的被调用链上层是acquire(1),这里的acquires为1
* 相当于state自增
*/
int nextc = c + acquires;
if (nextc < 0) //nextc超过int类型表示的最大范围
throw new Error("Maximum lock count exceeded");
setState(nextc);//将state属性自增
return true;
}
return false;
}

此方法的基本逻辑是:如果没有任何线程获取到此锁,尝试CAS尝试更新state,若此CAS更新成功,则成功获取锁并将当前线程设置为锁的占有线程,若CAS更新抢购,则获取锁失败。若当前线程获取之前已经获取到此锁,则将重复获取到锁的次数state自增。

非公平锁(和公平锁)释放锁的方法unlock()底层调用父类Sync的tryAcquire(int)

        protected final boolean tryRelease(int releases) {
int c = getState() - releases;//传入的参数releases为1
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();//当前线程不是独占线程,即当前线程没获取到同步状态,怎么能释放同步状
boolean free = false;
if (c == 0) {
/**
* 锁当前被重复获取的次数为0,锁已经被彻底释放了,其他线程能获取此锁了
*/
free = true;
setExclusiveOwnerThread(null);
}
setState(c);//state属性自减
return free;
}

此方法的基本逻辑是:如果该锁被获取了n次,那么前(n-1)次tryRelease(int releases)方法必须返回false,而只有同步状态完全释放了,才能返回true。可以看到,该方法将同步状态是否为0作为最终释放的条件,当同步状态为0时,将占有线程设置为null,并返回true,表示释放成功。

4.公平锁与非公平锁

获取同步状态的所有线程都经AQS的静态内部类Node包装成一个Node对象,所有线程都在这由Node构建的先进先出的同步队列中。如果锁是公平锁,那么锁的获取顺序就应该符请求的绝对时间顺序,先请求锁的线程优先获取锁,即也就是先进先出。

公平锁获取锁调用的底层方法tryAcquire(int)

protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
/*
*与nonfairTryAcquire(int)方法相比,此处有些不同,
* 只是多了"hasQueuedPredecessors()",没有前驱节点,才进行CAS更新state。
* (每个节点代表一个线程)只有在没有其他线程比当前线程等待更久的情况下才尝试获取锁
*/
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
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;
}

可以看出:此方法与nonfairTryAcquire(int)方法相似,只是多了”是否没有前驱节点的判断",只有在没有其他线程比当前线程等待更久的情况下当前线程才会尝试获取锁。

下面进行重入和(非)公平的相关测试

LockTest类中自定义一个锁CustomLock,它继承自ReentrantLock,与ReentrantLock相比,只添加一个"getQueuedThreadNames()"用来返回在同步队列中等待的线程名。

package juc;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock; public class LockTest {
private static final CustomLock fairLock = new CustomLock(true);
private static final CustomLock unfairLock = new CustomLock(false); public static void printText(boolean fair) {
final CustomLock lock = fair ? fairLock : unfairLock;
lock.lock();
try {
Thread t = Thread.currentThread();
String pName = t.getName();
System.out.print("线程[" + pName + "]第1次重入。"); lock.lock();
try {
System.out.print("线程[" + pName + "]第2次重入。");
System.out.println("阻塞的线程有" + lock.getQueuedThreadNames());
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
} public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
//非公平锁测试
new Thread(() -> {
LockTest.printText(false); LockTest.printText(false);
LockTest.printText(false); }, "pid" + (i + 1)).start();
//公平锁测试
// new Thread(() -> {
// LockTest.printText(true);
//
// LockTest.printText(true);
// LockTest.printText(true);
//
// }, "pid" + (i + 1)).start(); }
} private static class CustomLock extends ReentrantLock {
CustomLock() {
super();
} CustomLock(boolean fair) {
super(fair);
} List<String> getQueuedThreadNames() {
Collection<Thread> threads = super.getQueuedThreads();
List<String> tNames = new ArrayList<>(threads.size());
threads.forEach((thread) -> tNames.add(thread.getName()));
return tNames;
}
}
}

LockTest

控制台打印输出

非公平锁
公平锁

可以明显看出:非公平性锁出现了一个线程连续获取锁的情况,而公平性锁每次都是从同步队列中的第一个节点获取到锁(等待时间最久的线程)。

非公平锁可能存在的问题:

在nonfairTryAcquire(int acquires)方法,当一个线程请求锁时,只要获取了同步状态即成功获取锁。在这个前提下,刚释放锁的线程再次获取同步状态的几率会非常大,使得其他线程只能在同步队列中等待。非公平锁可能造成某些线程总是连续获取到锁,而一些线程长期获取不到锁。虽然一些线程获取不到锁,会造成线程“饥饿”,但同一个线程连续获取锁,却减少了线程上下文切换造成的资源消耗,整体上能提高系统的吞吐量。

参考:《Java并发编程的艺术》方腾飞

可重入排他锁ReentrantLock源码浅析的更多相关文章

  1. concurrent(三)互斥锁ReentrantLock & 源码分析

    参考文档:Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock:http://www.cnblogs.com/skywang12345/p/3496101.html Reentr ...

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

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

  3. Java并发之ReentrantLock源码解析(二)

    在了解如何加锁时候,我们再来了解如何解锁.可重入互斥锁ReentrantLock的解锁方法unlock()并不区分是公平锁还是非公平锁,Sync类并没有实现release(int arg)方法,这里会 ...

  4. ReentrantLock(重入锁)的源码解析

    转自:从源码角度彻底理解ReentrantLock(重入锁)](https://www.cnblogs.com/takumicx/p/9402021.html)) 公平锁内部是FairSync,非公平 ...

  5. ZooKeeper 分布式锁 Curator 源码 02:可重入锁重复加锁和锁释放

    ZooKeeper 分布式锁 Curator 源码 02:可重入锁重复加锁和锁释放 前言 加锁逻辑已经介绍完毕,那当一个线程重复加锁是如何处理的呢? 锁重入 在上一小节中,可以看到加锁的过程,再回头看 ...

  6. ZooKeeper 分布式锁 Curator 源码 03:可重入锁并发加锁

    前言 在了解了加锁和锁重入之后,最需要了解的还是在分布式场景下或者多线程并发加锁是如何处理的? 并发加锁 先来看结果,在多线程对 /locks/lock_01 加锁时,是在后面又创建了新的临时节点. ...

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

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

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

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

  9. ReentrantLock 锁释放源码分析

    ReentrantLock 锁释放源码分析: 调用的是unlock 的方法: public void unlock() { sync.release(1); } 接下来分析release() 方法: ...

随机推荐

  1. uni-app小程序组建

    (1)新建组建:编辑器右击 新建组建 (2)传值 <template> <view class="myRankingList"> <block v-f ...

  2. 51nod 1416:两点 深搜

    1416 两点 题目来源: CodeForces 基准时间限制:1 秒 空间限制:131072 KB 分值: 20 难度:3级算法题  收藏  关注 福克斯在玩一款手机解迷游戏,这个游戏叫做" ...

  3. 题解:luogu P1247

    大概没你们说得复杂吧...... \(Part\;1\) \(Nim\)游戏 大家都对异或和感到懵逼吧(排除大佬),其实很简单,用\(SG\)函数打表计算即可解决: 抛个板子: void get_sg ...

  4. Java 将数据写入磁盘并读取磁盘上的文件

    package test; import java.io.BufferedReader;import java.io.FileReader;import java.io.FileWriter;impo ...

  5. Tornadao Cookie

    cookie的详细介绍.Tronado带签名的cookie原理.基于cookie实现用户验证 cookie详细介绍 cookie本质就是存于浏览器的 键值对. 特性: 每次http请求服务端的时候,都 ...

  6. java中关键字super

    super关键字的作用 java中的super关键字是一个引用变量,用于引用父类对象.关键字“super”以继承的概念出现在类中. 主要用于以下情况:1.调用父类的方法   2.调用父类的变量  3. ...

  7. jQuery 里的 Promise

    1  $.ajax("test.html").done(function(){ alert("哈哈,成功了!"); }).fail(function(){ al ...

  8. BMP字节流转成CBitmap类

    BYTE* m_pBmpData = NULL; BITMAPINFO* m_pBmpInfo = new BITMAPINFO[sizeof(BITMAPINFOHEADER)+sizeof(RGB ...

  9. 一次C语言编程遇到的问题总结

    今天用C语言做了一个简单的用户登录注册存取款等功能的系统,发现有很多功能并不会实现,大概是使用Java太多了导致许多C的知识都忘记了,现在把碰到的问题总结如下: 1.字符串复制问题 java等一些编程 ...

  10. Andriod studio 汉化教程

    1.找到安装目录,D:\Program Files\Android\Android Studio\lib,先保存resources_en,并且把它的名字改为resources_cn.jar 2.下载汉 ...