前言

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. Scala教程之:可扩展的scala

    文章目录 隐式类 限制条件 字符串插值 s 字符串插值器 f 插值器 raw 插值器 自定义插值器 Scala是扩展的,Scala提供了一种独特的语言机制来实现这种功能: 隐式类: 允许给已有的类型添 ...

  2. Linux系统管理第四次作业 磁盘管理 文件系统

    1.为主机新增两块30GB的SCSI硬盘 2.划分3个主分区,各5GB,剩余空间作为扩展分区 [root@localhost ~]# fdisk /dev/sdb 欢迎使用 fdisk (util-l ...

  3. 实现QQ内打开链接跳转至浏览器

    经常遇到域名拦截的问题,不管是QQ还是微信在移动端如果打开被拦截是件很麻烦的事情. 那么,你怎样才能有效地避免这个问题呢?很多站长说域名可以抵御拦截?但是你有没有想过域名拦截的机制是什么? <? ...

  4. Android Studio SVN配置忽略文件

    1.用Android Studio创建一个项目,会在根目录和Module目录下自动生成.gitignore文件,貌似是Git的配置文件,和SVN没有关系. 2.打开Setting-Version Co ...

  5. 前端存储 (5) - service worker 离线存储

    service worker 离线存储 简介: 一般的网站 在我们无法访问的 时候 一般 回出现 如下 该网页无法访问 service worker 构建的网站不会出现这个错误,因为所有的 请求都是先 ...

  6. C++编程入门题目--No.2

    题目:企业发放的奖金根据利润提成.利润(I)低于或等于10万元时,奖金可提10%:利润高 于10万元,低于20万元时,低于10万元的部分按10%提成,高于10万元的部分,可可提 成7.5%:20万到4 ...

  7. POJ1088 滑雪题解+HDU 1078(记忆化搜索DP)

    Description Michael喜欢滑雪百这并不奇怪, 因为滑雪的确很刺激.可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你.Michael想知道 ...

  8. python(configparser 模块)

    1.下载安装 configparser 第三方模块 pip install configparser 2.读取配置文件 #配置文件内容如下 """ "D:/co ...

  9. TSP变形(三进制状压)

    题目:HDU3001 #include <bits/stdc++.h> using namespace std; ],vis[][],dis[][]; ][]; void init()// ...

  10. 环境篇:Superset

    环境篇:Superset Superset 是什么? Apache Superset 是一个开源.现代.轻量的BI分析工具,能够对接多种数据源,拥有丰富的图表展示形式.支持自定义仪表盘,用户界面友好, ...