1.定义

重入锁:能够支持一个线程对资源的重复加锁,也就是当一个线程获取到锁后,再次获取该锁时而不会被阻塞。

2.可重入锁的应用场景

2.1 如果已经加锁,则不再重复加锁,比如:交互界面点击后响应时间长,可能会多次点击,使用重入锁可防止后台重复执行。

if (lock.tryLock()) {  //如果已经被lock,则立即返回false不会等待,达到忽略操作的效果
try {
//操作
} finally {
lock.unlock();
}
}

2.2 如果发现该操作已经在执行,则等待一段时间,超时则不再执行。

try {
if (lock.tryLock(5, TimeUnit.SECONDS)) { //如果已经被lock,尝试等待5s,看是否可以获得锁,如果5s后仍然无法获得锁则返回false继续执行
try {
//操作
} finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
e.printStackTrace(); //当前线程被中断时(interrupt),会抛InterruptedException
}

2.3 如果操作已经加锁,则等待一个一个加锁。可以防止资源使用冲突,保证同一个时间内只有一个操作可以使用该资源。

lock.lock(); //如果被其它资源锁定,会在此等待锁释放,也将处于阻塞状态

2.4 可中断锁:可响应中断操作,然后释放锁。

lock.lockInterruptibly();

3.原理分析

可重入锁在获取锁时,有公平和非公平获取锁两种方式,默认是后者。如果在绝对时间上,先对锁进行获取的请求先被满足,那么这个锁则是公平锁,也就是等待时间最长的线程优先获取锁。反之,则是非公平锁,非公平锁的效率较高。我们知道ReentrantLock

是通过使用自定义同步器来实现锁的获取和释放的,那么就看一下获取同步状态的代码。

3.1 非公平锁获取状态的代码

 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;
}

以上代码逻辑是这样的:当一个线程尝试获取锁时,先判断同步状态值是否为0,如果是,则该现场获取到锁,并返回true;如果同步状态值不为0,则判断该线程是否就是占用该锁的当前线程,如果是,则同步状态值增加,表示同一个线程多次加锁,并返回ture。否则,则获取同步状态值失败,返回false。

3.2 释放状态时的代码

        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;
}

在释放锁时,每释放一次,同步状态值就是减少,最终同步状态值为0时,表示完全释放锁,此时将占有线程设置为null,同时返回true。

3.3 公平锁获取状态时的代码

        /**
* 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 == 0) {
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方法唯一的区别就是增加了hasQueuedPredecessors()方法,该方法的作用就是判断同步队列中当前节点是否有前驱节点。如果该方法返回true,则表示有线程比当前线程更早地请求获取锁,因此需要等待前驱线程获取并释放锁之后才能

继续获取锁。

现在有个问题需要思考,为什么非公平锁效率更高?

在nonfairTryAcquire(int acquires)方法中,当一个线程请求锁时,只要获取了同步状态即成功获取锁。在这个前提下,刚释放锁的线程再次获取同步状态的几率会非常大,使得其他线程只能在同步队列中等待。它虽然造成了线程饥饿,但是确实极少的线程切换,

保证了更达的吞吐量。而公平性锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换。

ReentrantLock重入锁详解的更多相关文章

  1. “全栈2019”Java多线程第二十九章:可重入锁与不可重入锁详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  2. 从源码角度彻底理解ReentrantLock(重入锁)

    目录 1.前言 2.AbstractQueuedSynchronizer介绍 2.1 AQS是构建同步组件的基础 2.2 AQS的内部结构(ReentrantLock的语境下) 3 非公平模式加锁流程 ...

  3. java并发系列(四)-----源码角度彻底理解ReentrantLock(重入锁)

    1.前言 ReentrantLock可以有公平锁和非公平锁的不同实现,只要在构造它的时候传入不同的布尔值,继续跟进下源码我们就能发现,关键在于实例化内部变量sync的方式不同,如下所示: /** * ...

  4. ReentrantLock(重入锁)以及公平性

    ReentrantLock(重入锁)以及公平性 标签(空格分隔): java NIO 如果在绝对时间上,先对锁进行获取的请求一定被先满足,那么这个锁是公平的,反之,是不公平的,也就是说等待时间最长的线 ...

  5. java高并发系列 - 第12天JUC:ReentrantLock重入锁

    java高并发系列 - 第12天JUC:ReentrantLock重入锁 本篇文章开始将juc中常用的一些类,估计会有十来篇. synchronized的局限性 synchronized是java内置 ...

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

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

  7. Java多线程之ReentrantLock重入锁简介与使用教程

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6543947.html  我们知道,线程安全问题需要通过线程之间的同步来解决,而同步大多使用syncrhoize ...

  8. ReentrantLock 重入锁(下)

    前沿:       ReentrantLock 是java重入锁一种实现,在java中我们通常使用ReentrantLock 和 synchronized来实现锁功能,本篇通过例子来理解下Reentr ...

  9. ReentrantLock(重入锁)简单源码分析

    1.ReentrantLock是基于AQS实现的一种重入锁. 2.先介绍下公平锁/非公平锁 公平锁 公平锁是指多个线程按照申请锁的顺序来获取锁. 非公平锁 非公平锁是指多个线程获取锁的顺序并不是按照申 ...

随机推荐

  1. [日常] imap协议读取邮件

    telnet imap.sina.net 143 A01 LOGIN shihan@appdev.sinanet.com 密码 A02 list "" * //列出邮件夹 * LI ...

  2. Java开发笔记(五十五)关键字static的用法

    前面介绍嵌套类的时候讲到了关键字static,用static修饰类,该类就变成了嵌套类.从嵌套类的用法可知,其它地方访问嵌套类之时,无需动态创建外层类的实例,直接创建嵌套类的实例就行.其实static ...

  3. Java学习笔记——判断字符Character类

    常用方法 下面所说的均是静态方法,也就是可以不创建对象直接调用 例:Character.isLetter(char c); isLetter 判断参数是否为字母(不分大小写),返回结果 isDigit ...

  4. 【Java】List遍历时删除元素的正确方式

    当要删除ArrayList里面的某个元素,一不注意就容易出bug.今天就给大家说一下在ArrayList循环遍历并删除元素的问题.首先请看下面的例子: import java.util.ArrayLi ...

  5. 一个极为简单的方法实现本地(离线)yum安装rpm包

    首先,我要关心的问题仍然是如何离线或者本地yum安装rpm包?这其中的关键当然是获取rpm包到本地,这其中的麻烦事是去解析依赖关系.然而,我发现一个极为简单的方法可以不用操心rpm包依赖关系,不多不少 ...

  6. linux学习笔记-linux主机上传下载文件至linux虚拟机的方法

    我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! 1.上传文件 scp -r file 用户名@ip地址:目标目录 2.下载文件 scp -r 用户名@ip地址:文件 目标目录

  7. [ArcGIS API for JavaScript 4.8] Sample Code-Get Started-widgets简介

    [官方文档:https://developers.arcgis.com/javascript/latest/sample-code/intro-widgets/index.html] 一.Intro ...

  8. Bitmap上下合成图片

    合成两张图片,上下叠加的效果: /** * 把两个位图覆盖合成为一个位图,以底层位图的长宽为基准 * * @param backBitmap 在底部的位图 * @param frontBitmap 盖 ...

  9. 初使用maven遇到各种问题记录

    Cannot change version of project facet Dynamic Web Module to 2.5? 解决办法:将web.xml配置文件中的<web-app ver ...

  10. MySQL 基础知识梳理学习(四)----GTID

    在日常运维中,GTID带来的最方便的作用就是搭建和维护主从复制.GTID的主从模式代替了MySQL早期版本中利用二进制日志文件的名称和日志位置的做法,使用GTID使操作和维护都变得更加简洁和可高. 1 ...