交流群:462197261站长百科站长论坛热门标签收藏本站北冥有鱼 互联网前沿资源第一站 助力全行业互联网+
点击这里给我发消息
  • 当前位置:
  • 教你完全理解ReentrantLock重入锁

    1. ReentrantLock的介绍

    ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁,支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。在java关键字synchronized隐式支持重入性(关于synchronized可以看这篇文章),synchronized通过获取自增,释放自减的方式实现重入。与此同时,ReentrantLock还支持公平锁和非公平锁两种方式。

    那么,要想完完全全的弄懂ReentrantLock的话,主要也就是ReentrantLock同步语义的学习:1. 重入性的实现原理;2. 公平锁和非公平锁。

    2. 重入性的实现原理

    要想支持重入性,就要解决两个问题:

    1. 在线程获取锁的时候,如果已经获取锁的线程是当前线程的话则直接再次获取成功;

    2. 由于锁会被获取n次,那么只有锁在被释放同样的n次之后,该锁才算是完全释放成功。

    通过这篇文章,我们知道,同步组件主要是通过重写AQS的几个protected方法来表达自己的同步语义。

    针对第一个问题,我们来看看ReentrantLock是怎样实现的,以非公平锁为例,判断当前线程能否获得锁为例,核心方法为nonfairTryAcquire:

    final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    //1. 如果该锁未被任何线程占有,该锁能被当前线程获取
    if (c == 0) {
    if (compareAndSetState(0, acquires)) {
    setExclusiveOwnerThread(current);
    return true;
    }
    }
    //2.若被占有,检查占有线程是否是当前线程
    else if (current == getExclusiveOwnerThread()) {
    // 3. 再次获取,计数加一
    int nextc = c + acquires;
    if (nextc < 0) // overflow
    throw new Error("Maximum lock count exceeded");
    setState(nextc);
    return true;
    }
    return false;
    }
    
    

    这段代码的逻辑也很简单,具体请看注释。

    为了支持重入性,在第二步增加了处理逻辑,如果该锁已经被线程所占有了,会继续检查占有线程是否为当前线程,如果是的话,同步状态加1返回true,表示可以再次获取成功。

    每次重新获取都会对同步状态进行加一的操作,那么释放的时候处理思路是怎样的了?(依然还是以非公平锁为例)核心方法为tryRelease:

    protected final boolean tryRelease(int releases) {
    //1. 同步状态减1
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
    throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
    //2. 只有当同步状态为0时,锁成功被释放,返回true
    free = true;
    setExclusiveOwnerThread(null);
    }
    // 3. 锁未被完全释放,返回false
    setState(c);
    return free;
    }

    代码的逻辑请看注释,需要注意的是,重入锁的释放必须得等到同步状态为0时锁才算成功释放,否则锁仍未释放。如果锁被获取n次,释放了n-1次,该锁未完全释放返回false,只有被释放n次才算成功释放,返回true。

    到现在我们可以理清ReentrantLock重入性的实现了,也就是理解了同步语义的第一条。

    3. 公平锁与公平锁

    ReentrantLock支持两种锁:公平锁和非公平锁。

    何谓公平性,是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求上的绝对时间顺序,满足FIFO。ReentrantLock的构造方法无参时是构造非公平锁,源码为:

    public ReentrantLock() {
    sync = new NonfairSync();
    }
    

    另外还提供了另外一种方式,可传入一个boolean值,true时为公平锁,false时为非公平锁,源码为:

    public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
    }
    

    在上面非公平锁获取时(nonfairTryAcquire方法)只是简单的获取了一下当前状态做了一些逻辑处理,并没有考虑到当前同步队列中线程等待的情况。

    我们来看看公平锁的处理逻辑是怎样的,核心方法为:

    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的逻辑判断,方法名就可知道该方法用来判断当前节点在同步队列中是否有前驱节点的判断,如果有前驱节点说明有线程比当前线程更早的请求资源,根据公平性,当前线程请求资源失败。如果当前节点没有前驱节点的话,再才有做后面的逻辑判断的必要性。

    公平锁每次都是从同步队列中的第一个节点获取到锁,而非公平性锁则不一定,有可能刚释放锁的线程能再次获取到锁。

    公平锁 VS 非公平锁

    公平锁每次获取到锁为同步队列中的第一个节点,保证请求资源时间上的绝对顺序,而非公平锁有可能刚释放锁的线程下次继续获取该锁,则有可能导致其他线程永远无法获取到锁,造成“饥饿”现象。

    公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,ReentrantLock默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。

    以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持北冥有鱼。

    您可能感兴趣的文章:

    • 详解java并发之重入锁-ReentrantLock
    • Java源码解析之可重入锁ReentrantLock
    • ReentrantLock源码详解--条件锁
    • ReentrantLock源码详解--公平锁、非公平锁

    广而告之:
    热门推荐:
    asp.net 简单实现禁用或启用页面中的某一类型的控件

    比如,我们在提交一个表单的时候,可能由于网络或服务器的原因,处理很慢,而用户在处理结果出来之前反复点击按钮提交。这样很容易造成不必要的麻烦甚至是错误。说了这么多,其实就是要实现一个禁用某些控件的一种功能。好了,下面我就介绍自己简单实现的这个小功能,贴···

    PHP微信H5支付开发实例

    最近由于业务所需,对接了微信H5支付,然而微信支付对这块并没有现成的demo可用,所以就必须自己老老实实对照开发文档去写咯!但这对于刚接触的童鞋来说,坑多多少少还是有的,所以寻思着把自己的经验分享出来,毕竟现成的用的还是多巴适的嘛! 好了,官方文档的那一套就不多···

    织梦模板DEDECMS修改TAG TITLE字数限制的教程

    很多站长朋友会经常碰到这样的问题,我们在发布文档的时候,标调/TAG标签/描述如果想放多一点文字都不行,织梦系统会默认的截取,今天秀站网总结了教程,能解决这一问题,并且做了实验,安全无误。 原因分析:织梦(dedecms)的TAGS默认字数较少,只能写12个字符,多···

    利用纯js + transition动画实现移动端web轮播图详解

    前言 在上一篇文章中,我们使用 tween算法实现了 ease-out移动效果, 其实更简洁的方法是使用 css3的 transition动画,下面话不多说了,来一起看看详细的介绍吧。 核心点: 在 我们通过代码来移动一段距离的时候, 使用 transion动画;在手指移动的时候,不使用transition动画. 使···

    Three.js利用顶点绘制立方体的方法详解

    前言 之前我们在学些WebGL基础的时候每天都是在一直研究顶点位置,法向量,绘制下标什么的。虽然复杂,但是毕竟原生,性能没得说。 three.js也给我们提供了相关的接口供我们使用原生的方法绘制模型,下面话不多说了,来一起看看详细的介绍吧。 下面是我的个人一个案例。 首先,···

    AngularJs中$cookies简单用法分析

    本文实例讲述了AngularJs中$cookies简单用法。分享给大家供大家参考,具体如下: 首先导入angular-cookies.js 在App 中require模块ngCookies: angular.module("app",['ngCookies']); 使用格式:$cookies.变量名 = 变量值 不能使用: $cookies.变量名 = { 变量名1: 变量值,···

    帝国cms公文签收系统完美支持7.0,7.2版本

    帝国cms公文签收系统主要功能有发布公文、签收公文、附件公文、置顶公文、公文管理、用户管理、公文搜索等公文签收管理系统适合政府、司法、教育等部门使用。发布公文:可以选择全部会员用户或指定用户、用户进行签收。签收公文:签收公文后记录签收信息,实时显示签收状态。···

    JavaScript在网页中画圆的函数arc使用方法

    一、arc所需要的参数设置 复制代码 代码如下: arc(x, y, radius, startAngle, endAngle, counterclockwise); 其中x,y,radius都很容易理解,那么重点说说startAngle,endAngle和counterclockwise三个参数! 二、arc参数详解     1,startAngle和endAngle分别指···

    Discuz!5的PHP代码高亮显示插件(黑暗中的舞者更新)

    discuz!5.0论坛显示风格的php代码高亮显示插件, 把php代码进行语法着色,喜欢PHP的同志可以轻松的看代码啦。 同时添加了html代码运行功能。 此插件是在以前某位同志发布的Discuz!4的php代码高亮显示基础之上修改完成。 另外,本人的php学习进入了停滞状态, 希望有人能帮···

    PHP中spl

    本文实例分析了PHP中spl_autoload_register()函数用法。分享给大家供大家参考,具体如下: 在了解这个函数之前先来看另一个函数:__autoload。 一、__autoload 这是一个自动加载函数,在PHP5中,当我们实例化一个未定义的类时,就会触发此函数。看下面例子: printit.class.php···