前言

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

一、ReentrantLock的使用

ReentrantLock实现了JUC中的Lock接口,Lock接口定义了一套加锁和解锁的方法,方法如下:

     /**
* 加锁,如果加锁失败则会阻塞当前线程,直到加锁成功
*/
void lock(); /**
* 同上,不过会响应中断,当线程设置中断时会抛异常退出
*/
void lockInterruptibly() throws InterruptedException; /**
* 尝试加锁,不会阻塞当前线程,加锁失败则直接返回false,成功则返回true
*/
boolean tryLock(); /**
* 同上,不过有超时时间,当直到时间之后还是没有加锁成功,则返回false,成功则返回true
*/
boolean tryLock(long time, TimeUnit unit) throws InterruptedException; /**
* 解锁
*/
void unlock(); /**
* 创建Condition对象,用于实现线程的等待/唤醒机制
*/
Condition newCondition();

ReentrantLock使用案例如下:

         ReentrantLock lock = new ReentrantLock(true);//初始化Lock对象
lock.lock();//加锁操作
try{
//TODO do someThing
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();//解锁操作
}

ReentrantLock的使用比较简单,直接通过构造函数创建实例,分别调用lock方法加锁,unlock方法解锁即可。

ReentrantLock的构造方法有两个分别如下:

 /**默认构造函数,默认采用非公平锁*/
public ReentrantLock() {
sync = new NonfairSync();
} /**传入fair字段表示是否采用公平锁,true为公平锁;false为非公平锁*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

ReentrantLock支持公平锁和非公平锁两种锁机制,公平锁则表示同步的队列是FIFO模式的,等待时间最长的线程先获取锁;非公平模式则表示获取锁的线程完全随机,看CPU分配给哪个线程就由哪个线程获取锁。

二、ReentrantLock的实现原理解析

ReentrantLock的实现原理全部是通过其内部类Sync实现的,Sync集成于AQS并重写了AQS的获取和释放同步状态的方法,源码如下:

Reentrantock的加锁和解锁方法都是调用了内部类Sync的对应方法

 /**加锁方法*/
public void lock() {
sync.lock();
} /**尝试加锁方法*/
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
} /**解锁方法*/
public void unlock() {
sync.release(1);
} /**创建Condition对象*/
public Condition newCondition() {
return sync.newCondition();
} /**判断当前线程是否独占锁*/
public boolean isHeldByCurrentThread() {
return sync.isHeldExclusively();
}

所以探究ReentrantLock的实现原理,主要是看内部类Sync的实现逻辑,而ReentrantLock类中除了有内部类Sync,还有Sync的两个子类(公平同步器)FairSync和(非公平同步器)NonFairSync,Sync的子类分别重写了Sync的lock方法和tryAcquire方法,

FairSync实现的是公平锁的效果,NonFairSync实现的是非公平锁的效果。

公平锁实现源码:

 /** class FairSync * /

 /**公平锁*/
final void lock() {
acquire(1);//调用AQS的acquire方法
}

AQS的acquire实际是调用了子类的tryAcquire方法,FairSync的tryAcquire方法源码如下:

 /**公平加锁*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();//获取当前线程
int c = getState();//获取当前同步状态
if (c == 0) {//当状态为0时表示锁没有被占有
/**
* 尝试获取锁
* hasQueuedPredecessors()方法判断当前线程是否是head节点的后继节点
* compareAndSetState()通过CAS来设置AQS的state值
* */
if (!hasQueuedPredecessors() &&
12 compareAndSetState(0
, acquires)) {
/**设置当前线程为占用锁的线程*/
setExclusiveOwnerThread(current);
return true;
}
}
/**
* 当状态不为0时,表示锁已经被占用,此时判断当前线程是否是占用锁的线程
* getExclusiveOwnerThread()方法返回当前占用锁的线程
* */
else if (current == getExclusiveOwnerThread()) {
/**
* 如果当前线程已经占有了锁,则修改状态+1,表示占用了锁次数+1
* 所以可重入锁的实现原理就是state值 + 1
* */
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
/**设置同步状态值 = 旧状态值 + 1*/
setState(nextc);
return true;
}
return false;
}

从源码看出,公平锁的实现逻辑实际就是遵循FIFO原则,尝试获取锁的前提是必须当前线程是同步队列head节点的后继节点,这样就保证了获取锁的顺序是完全按照同步队列的节点顺序获取的。

非公平锁实现原理:

     /**非公平锁加锁*/
final void lock() {
/**直接通过CAS尝试设置同步状态,
* 成功则调用setExclusiveOwnerThread方法设置当前线程为占用锁的线程*/
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
/**失败则调用AQS的acquire方法,实际是调用tryAcquire方法*/
acquire(1);
} protected final boolean tryAcquire(int acquires) {
/**调用Sync的nonfairTryAcquire方法*/
return nonfairTryAcquire(acquires);
} /**非公平获取同步状态*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
/**
* 判断当前同步状态值,值为0则表示可以获取锁
* 直接通过CAS设置同步状态,成功则获取锁成功
* */
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
/**
* 当锁被占用后判断是否占用锁的线程是否是当前线程
* 如果是则状态值+1,表示锁可以重入
* */
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;
}

通过源码可以看出非公平锁和加锁流程和公平锁的加锁流程基本上一致,只是公平锁加锁之前需要判断当前线程是否是同步队列head节点的后继节点,而非公平锁无需判断直接可以尝试加锁。所以效率上非公平锁的效率更高。

解锁源码解析:

 /**ReentrantLock的解锁方法直接调用Sync的解锁方法
* release()是父类AQS的方法,实际是调用了子类的tryRelease方法*/
public void unlock() {
sync.release(1);
} /**Sync重写了AQS的tryRelease方法*/
protected final boolean tryRelease(int releases) {
/**1. 获取同步状态 -1 表示释放一次锁*/
int c = getState() - releases;
/**2. 判断当前线程是否是当前占有锁的线程*/
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
/**3. 如果同步状态值为0,表示锁完全释放了,则清除当前线程为占有锁的线程
* 如果同步状态值不为0,则表示加锁的次数多于解锁的次数,还需要继续解锁*/
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
/**设置同步状态值为最新的状态值*/
setState(c);
return free;
}

可以看出释放锁时就是将同步状态的值减1,直到同步状态的值变成0才表示完全释放成功,否则都会返回false表示释放锁失败。

三、ReentrantLock和Synchronzied的相同点和不同点?

相同点:

1、可重入锁,ReentrantLock和Synchronzied都是可重入锁,获取锁的线程都可以重复获取锁成功

2、独占式锁,ReentrantLock和Synchronzied都是独占式锁,同一时刻都只允许一个线程获取锁

不同点:

1、Synchronzied可重入是隐式的,解锁是自动释放的,释放之前都可以重入;ReentrantLock可重入是显式的,需要执行多次加锁和多次释放锁操作,且加锁和解锁的次数需要完全一致,否则可能会导致其他线程无法获取到锁。

2、Synchronized是非公平锁,多线程竞争锁成功与否看各个线程自行争取;ReentrantLock同时支持公平锁和非公平锁,默认是非公平锁和Synchronzied一样,而公平锁就遵循FIFO原则,先进入等待队列中的线程优先获取锁

3、Synchronzied不需要手动释放锁;ReentrantLock需要手动释放锁,如果不释放其他线程就无法获取锁,所以释放锁需要放在finally中执行

4、Synchronzied不可响应中断,获取不到就会一直阻塞直到获取锁成功;ReentrantLock支持响应中断,可以通过设置中断标识来中断阻塞的线程

5、Synchronzied是通过获取对象的Monitor对象来实现的;ReentrantLock是通过AQS的子类来实现的

四、ReentrantLock注意事项?

1、ReentrantLock的可重入性是显式的

ReentrantLock可重入性实际就针对同步状态自增或自减操作,重入了多少次锁就必须对应的释放多少次锁,而不可以进多次而只释放一次,只有当前释放次数和加锁次数一样时才算真正的释放成功。

2、公平锁和非公平锁比较

公平锁遵循FIFO原则保证了获取锁的顺序和同步队列中的线程顺序一致,但是性能上比非公平锁差很多,因为需要不停的CPU线程切换。

非公平锁性能更好,没有多余的CPU线程切换消耗,但是极端情况下会出现“饥饿线程”问题(某些线程始终抢不到锁而一致等待着)

Java并发包4--可重入锁ReentrantLock的实现原理的更多相关文章

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

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

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

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

  3. Java 重入锁 ReentrantLock 原理分析

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

  4. java 可重入锁ReentrantLock的介绍

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

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

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

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

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

  7. 17_重入锁ReentrantLock

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

  8. Java中可重入锁ReentrantLock原理剖析

    本文由码农网 – 吴极心原创,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划! 一. 概述 本文首先介绍Lock接口.ReentrantLock的类层次结构以及锁功能模板类AbstractQue ...

  9. Java多线程——深入重入锁ReentrantLock

    简述 ReentrantLock 是一个可重入的互斥(/独占)锁,又称为“独占锁”. ReentrantLock通过自定义队列同步器(AQS-AbstractQueuedSychronized,是实现 ...

随机推荐

  1. HDU 6341 Let Sudoku Rotate

    #include<bits/stdc++.h> using namespace std; #define rep(i,a,b) for(int i=a;i<=b;++i) #defi ...

  2. MFC之动态调用自己写的类库中的类的成员方法

    第一步:创建一个要调用的类库 如果是MFC程序使用,可以创建一个MFC的类库,不过依然可以创建一个win32类库.我所知道的,MFC的类库可以分为常规MFC DLL和MFC扩展DLL关于它们之间的区别 ...

  3. POJ 3581 Prime Gap(二分)

    Prime Gap Time Limit: 5000MS Memory Limit: 65536K Total Submissions: 11009 Accepted: 6298 Descriptio ...

  4. 图论--Floyd总结

    Key word:     ①最短路     ②传递闭包:大小关系 数值关系 先后关系 联通关系     ③floyd变形     ④实现方式:插点发法     ⑤思想:动态规划 1.最短路: 最短路 ...

  5. zabbix 数据库分区表配置

    下载 pwd /usr/local/zabbix/share/zabbix/externalscriptswget http://cactifans.hi-www.com/zabbix/partiti ...

  6. Java笔记(day20-22)

    IO流: 输入流.输出流 字节流.字符流:为了处理文字数据方便而出现的对象. (其实这些对象的内部使用的还是字节流(因为文字最终也是字节数据,只不过,通过字节流读取了相对应的字节数,没有对这些字节直接 ...

  7. Java——Spring依赖配置详解

    <properties> <junit.version>4.12</junit.version> <spring.version>4.3.9.RELEA ...

  8. 使用kubeadm部署k8s集群[v1.18.0]

    使用kubeadm部署k8s集群 环境 IP地址 主机名 节点 10.0.0.63 k8s-master1 master1 10.0.0.63 k8s-master2 master2 10.0.0.6 ...

  9. Phoenix and Distribution(字典序贪心)

    \(给定一串字母,分成k份,使得最大字典序最小.(字母可以任意组合)\) \(------------------------------issue~------------------------\ ...

  10. P1666前缀单词

    题目传送门点我传送 Ⅰ.字典树+树型DP 非常奇妙的一种解法 第一部分:构建树 先对来的单词读入,插入字典树 然后对于一颗字典树,其实是有很多无用边的,所以我们需要删去一些边 删去非单词节点和非单词节 ...