synchronized 和 reentrantlock 区别是什么_JUC源码系列之ReentrantLock源码解析

f78ad27c800b0a907dc87ae2aa74b3dd.png

目录

  • ReentrantLock 简介
  • ReentrantLock 使用示例
  • ReentrantLock 与 synchronized 的区别
  • ReentrantLock 实现原理
  • ReentrantLock 源码解析
a4ceffdeca1a5b9d7d79f4a28f03a63c.png

ReentrantLock 简介

ReentrantLock 是 JDK 提供的一个可重入的独占锁,

  • 独占锁:同一时间只有一个线程可以持有锁
  • 可重入:持有锁的线程可重复加锁,意味着第一次成功加锁时,需要记录锁的持有者为当前线程,后续再次加锁时,可以识别当前线程。

ReentrantLock 提供了公平锁以及非公平锁两种模式,要解释这两种模式不异同,得先了解一下 ReentrantLock 的加锁流程,ReentrantLock 基于 AQS 同步器实现加解锁,基本的实现流程为:

线程 A、B、C 同时执行加锁,加锁是通过CAS操作完成,CAS 是原子操作,可以保证同一时间只有一个线程加锁成功,假设线程 A 加锁成功,则线程 B、C 进入 AQS 等待队列并被挂起,假设 B 在前,C 在后,当线程 A 释放锁时,会唤醒排在等待队列队首的线程 B,该线程会尝试通过 CAS 进行获取锁。如果线程 B 尝试加锁的同时,有线程 D 也同时进行加锁,如果线程 D 与 线程 B 竞争加锁,则为非公平锁,线程 D 加入等待队列排在线程 C 之后,则为公平锁。

  • 非公平锁:加锁时会与等待队列中的头节点进行竞争。
  • 公平锁:加锁时首先判断等待队列中是否有线程在排队,如果没有则参与竞争锁,如果有则排队等待。

所谓公平就是,大家一起到,就竞争上岗,如果已经有人在排队了,那就先来后到。

使用示例

使用伪代码表示

class X {    private final ReentrantLock lock = new ReentrantLock();    public void m() {        lock.lock();  // block until condition holds        try {            // ... method body        } finally {            lock.unlock();        }    }}

默认实现的是非公平锁,如果要使用公平锁,只需要创建 ReentrantLock 对象时传递入参 true 即可,使用方法与非公平锁一样。

private final ReentrantLock lock = new ReentrantLock(true);

condition 使用示例:

class X {    private final ReentrantLock lock = new ReentrantLock();    private final Condition condition = lock.newCondition();    public void poll() {        lock.lock();  // block until condition holds        try {            while(条件判断表达式) {                condition.wait();            }        } finally {            lock.unlock();        }    }        public void push() {        condition.signal();    }}

ReentrantLock 与 synchronized 的区别

ReentrantLock 提供了 synchronized 类似的功能和内存语义。

相同点

  • ReentrantLock 与 synchronized 都是独占锁,可以让程序正确同步。
  • ReentrantLock 与 synchronized 都可重入锁,可以在循环中使用 synchronized 进行加锁并不用担心解锁问题,但 ReentrantLock 则必须要进行与加锁相同次数的解锁操作,不然可能导致没有真正解锁成功。

不同点

  • synchronized 是JDK提供的语法,加锁解锁的过程是隐式的,用户不用手动操作,操作简单,但不够灵活。
  • ReentrantLock 需要手动加锁解锁,且解锁次数必须与加锁次数一样,才能保证正确释放锁,操作较为复杂,但是因为是手动操作,所以可以应付复杂的并发场景。
  • ReentrantLock 可以实现公平锁
  • ReentrantLock 可以响应中断,使用 lockInterruptibly 方法进行加锁,可以在加锁过程中响应中断,synchronized 不能响应中断
  • ReentrantLock 可以实现快速失败,使用 tryLock 方法进行加锁,如果不能加锁成功,会立即返回 false,而 synchronized 是阻塞式。
  • ReentrantLock 可以结合 Condition 实现条件机制。

可以看到,ReentrantLock 与 synchronized 都是实现线程同步加锁,但 ReentrantLock 比起 synchronized 要灵活很多。

a43a402d693fa2074913cdf259d05301.png

实现原理

ReentrantLock 使用组合的方式,通过继承 AQS 同步器实现线程同步。通过控制 AQS 同步器的同步状态 state 达到加锁解锁的效果,该状态默认为 0,代表锁未被占用,加锁则是通过 cas 操作将其设置为 1,cas 是原子性操作,可以保证同一时间只有一个线程可以加锁成功,同一个线程可以重复加锁,每次加锁同步状态自增 1,释放锁的过程就是将同步状态自减,减到 0 时才算完全释放,这也解释了为什么释放锁的次数必须与加锁次数一样的问题,因为只有次数一样才能将同步状态减至 0,这样其它线程才能进行加锁。

源码分析

Lock 接口

ReentrantLock 实现了 Lock 接口,这是 JDK 提供的所有 JVM 锁的基类。

public interface Lock {    // 阻塞式加锁    void lock();    // 阻塞式加锁,但可以响应中断,加锁过程中线程中断,抛出 InterruptedException 异常    void lockInterruptibly() throws InterruptedException;    // 快速失败加锁,只尝试一次,    boolean tryLock();    // 阻塞式加锁,可以响应中断并且实现超时失败    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;    // 释放锁    void unlock();    // 实现条件    Condition newCondition();}

通过代码可以看到,ReentrantLock 的内部实现都是通过 Sync 这个类实现,可以认为遵守组合设计原则,Sync 是 ReentrantLock 的内部类。这里的方法调用,并没有区分是公平锁还是非公平锁,而是无差别地调用,所以区别一定在 Sync 这个类的实现中。

public class ReentrantLock implements Lock, java.io.Serializable {    private final Sync sync;    public void lock() {        sync.lock();    }    public void lockInterruptibly() throws InterruptedException {        sync.acquireInterruptibly(1);    }    public boolean tryLock() {        return sync.nonfairTryAcquire(1);    }    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {        return sync.tryAcquireNanos(1, unit.toNanos(timeout));    }    public void unlock() {        sync.release(1);    }    public Condition newCondition() {        return sync.newCondition();    }}

Sync 类继承了 AQS 同步器,通过同步器实现线程同步,因为是独占锁,所以最重要的就是实现 tryAcquire 与 tryRelease 两个方法,Sync 类是一个 abstract 类,它拥有两个实现类 FairSync 与 NonfairSync,通过名字应该就可分辨他们就是公平锁与非公平锁。

abstract static class Sync extends AbstractQueuedSynchronizer {    abstract void lock();    // 非公平加锁    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()) { // 如果锁已经被当前线程占有,说明是重复加锁            int nextc = c + acquires; // 将同步状态进行自增,acquires 的传值为 1            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; // 将同步状态进行自减,acquires 的传值为 1        if (Thread.currentThread() != getExclusiveOwnerThread())            throw new IllegalMonitorStateException();        boolean free = false;        if (c == 0) { // 当同步状态减成 0 时,代表完全释放锁,将锁的拥有者置空            free = true;            setExclusiveOwnerThread(null);        }        setState(c);        return free;    }    // ...省略其它...}

所以公平锁与非公平锁的玄机就在 ReentrantLock 的构造方法中,默认的无参构造方法创建非公平锁,如果传参 true,则创建公平锁。而这两个锁都是 Sync 的子类,使用了不同的实现策略,可以认为使用了策略模式。

public class ReentrantLock implements Lock, java.io.Serializable {    // 默认创建非公平锁    public ReentrantLock() {        sync = new NonfairSync();    }    // 如果为 true,则创建公平锁    public ReentrantLock(boolean fair) {        sync = fair ? new FairSync() : new NonfairSync();    }}

接下来分别看一下 FairSync 与 NonfairSync 是如何实现公平锁与非公平锁的,首先分析非公平锁

static final class NonfairSync extends Sync {    // 阻塞式加锁    final void lock() {        // 首先尝试竞争加锁,如果成功则设置当前线程为锁的拥有者        if (compareAndSetState(0, 1))             setExclusiveOwnerThread(Thread.currentThread());        else            acquire(1); // 使用 AQS 排队    }    // 尝试加锁    protected final boolean tryAcquire(int acquires) {        return nonfairTryAcquire(acquires);    }}

阻塞式加锁调用 ReentrantLock 的 lock() 方法,该方法调用 sync.lock() 执行加锁,非公平锁也就是调用 NonfairSync 类的 lock 方法,该方法首先尝试竞争加锁,此时有三种情况:

  • 此时锁没有人持有,竞争成功,直接设置当前线程为锁的拥有者并返回
  • 此时锁没有人持有,竞争失败,走 AQS 加锁流程
  • 此时锁被其它线程拥有,走 AQS 加锁流程
  • 此时锁被自己拥有,竞争失败,走 AQS 加锁流程

AQS 加锁流程就是调用 tryAcquire 方法尝试加锁,如果成功则返回加锁成功,如果失败则进入等待队列并挂起,等待锁的持有者释放锁时唤醒等待队列中的线程,并再次尝试加锁,如此反复,直到加锁成功。

public final void acquire(int arg) {    if (!tryAcquire(arg) &&        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))        selfInterrupt();}

AQS 加锁流程在 AQS 中已经提供了完整实现模板,不需要去了解底层就可以使用,需要做的就是自行实现 tryAcquire 方法,NonfairSync 的 tryAcquire 方法这里再贴一次实现代码。

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()) { // 如果锁已经被当前线程占有,说明是重复加锁        int nextc = c + acquires; // 将同步状态进行自增,acquires 的传值为 1        if (nextc < 0) // overflow            throw new Error("Maximum lock count exceeded");        setState(nextc);        return true;    }    return false;}

接下来看一下 FairSync 的实现

static final class FairSync extends Sync {    final void lock() {        acquire(1);    }    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; // 将同步状态进行自增,acquires 的传值为 1            if (nextc < 0)                throw new Error("Maximum lock count exceeded");            setState(nextc);            return true;        }        return false;    }}

释放锁的过程,公平锁与非公平锁是一样的,前面的代码中已经解释过了,这里就不再多说了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/396422.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

gulp 和npm_为什么我离开Gulp和Grunt去看npm脚本

gulp 和npmI know what you’re thinking. WAT?! Didn’t Gulp just kill Grunt? Why can’t we just be content for a few minutes here in JavaScript land? I hear ya, but…我知道你在想什么 WAT &#xff1f;&#xff01; 古尔普不是杀死了咕unt吗&#xff1f; 为什么…

mysql8.0递归_mysql8.0版本递归查询

1.先在mysql数据库添加数据DROP TABLE IF EXISTS dept;CREATE TABLE dept (id int(11) NOT NULL,pid int(11) DEFAULT NULL,name varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,date datetime(0) DEFAULT NULL,PRIMARY KEY (id) USING BTREE) ENGINE…

js 轮播插件

flexslider pc插件 个人用过flickerplate 移动端插件 个人用过个人觉得比较好的移动端插件swiper http://www.swiper.com.cn/ 用过个人觉得比较好的pc端插件待定

计算机中的字符编码

字符编码 什么是计算机编码 计算机只能处理二进制的数据&#xff0c;其它的数据都要进行转换&#xff0c;但转换必须要有一套字符编码(是字符与二进制的一个对应关系)。常用的字符&#xff1a;a-z、0-9、其它的符号等&#xff0c;计算机也不能直接处理。 &#xff08;字符编码类…

致力微商_致力于自己。 致力于公益组织。

致力微商by freeCodeCamp通过freeCodeCamp 致力于自己。 致力于公益组织。 (Commit to Yourself. Commit to a Nonprofit.) In case you missed it, our October Summit was jam-packed with several big announcements about our open source community.如果您错过了它&#…

应急照明市电检测_应急照明如何供电? 如何接线? 图文分析!

对于大部分刚接触建筑电气设计的工作者来说&#xff0c;应急照明的强启原理一直都是很头疼的问题。由于不知道应急照明的强启原理&#xff0c;所以&#xff0c;应急灯具应该用多少根线&#xff0c;其实也就无从谈起。下面以文字和图片结合的方式来说明应急灯怎么接线的&#xf…

win10网速慢

升级到win10之后发现网速特别慢&#xff0c;搜了下&#xff0c;网上的解决办法果然好使&#xff0c;按照如下操作即可。 返回桌面&#xff0c;按WINR键组合&#xff0c;运行gpedit.msc 打开组策略 依次展开管理模板-》网络-》QoS数据计划程序-》限制可保留宽带&#xff0c;双击…

ubuntu安装nodejs

下载nodejs https://nodejs.org/dist/v4.6.0/node-v4.6.0-linux-x64.tar.gz 解压 tar -zxvf node-v4.6.0-linux-x64.tar.gz 移动到/opt/下 mv node-v4.6.0-linux-x64 /opt/ 创建链接 ln -s /opt/node-v4.6.0-linux-x64/bin/node /usr/local/bin/node 转载于:https://www.cnblog…

android实用代码

Android实用代码七段&#xff08;一&#xff09; 前言 这里积累了一些不常见确又很实用的代码&#xff0c;每收集7条更新一次&#xff0c;希望能对大家有用。 声明 欢迎转载&#xff0c;但请保留文章原始出处:)     博客园&#xff1a;http://www.cnblogs.com 农民伯伯&…

mysql 全文本检索的列_排序数据列以检索MySQL中的最大文本值

为此&#xff0c;您可以将ORDER BY与一些聚合函数right()一起使用。让我们首先创建一个表-create table DemoTable1487-> (-> StudentCode text-> );使用插入命令在表中插入一些记录-insert into DemoTable1487 values(560);insert into DemoTable1487 values(789);in…

关于长寿_FreeCodeCamp可以帮助您更长寿

关于长寿by Christopher Phillips克里斯托弗菲利普斯(Christopher Phillips) 免费代码营可能帮助您长寿 (Free Code Camp Might Help You Live Longer) Since I started my web development journey with Free Code Camp, I’ve felt more awake, more alert, and able to pro…

python世界你好的输出便携电源适配器_65W PD输出,Thinkplus USB-C便携电源适配器(PA65)开箱评测...

包装盒底盖面为红色&#xff0c;标注了产品的相关参数&#xff1a;型号&#xff1a;PA65&#xff1b;输入&#xff1a;100V-240V~50/60Hz 1.5A&#xff1b;输出&#xff1a;5V/3A、9V/3A、12V/3A、15V/3A、20V/3.25A&#xff1b;制造商&#xff1a;南京博兰得电子科技有限公司&…

归并排序与逆序对

在刷题的过程中碰到了关于无序序列的逆序对统计的问题。 直接暴力会超时&#xff0c;然后搜索了一下算法&#xff0c;发现可以通过归并排序的思想来做到这个统计的过程。看代码的时候&#xff0c;不知道自己的理解力不够还是不熟悉别人的代码&#xff0c;反正是看不懂。无奈之下…

c#获取pdf文件页数

引用命名空间&#xff1a;using iTextSharp.text.pdf; string filePath Server.MapPath("/upload/123.pdf"); //文件的物理路径PdfReader reader new PdfReader(filePath);int iPageNum reader.NumberOfPages; //文件页数reader.Close();Response.Write("文件…

VS2015 Cordova Ionic移动开发(五)

一、创建侧边菜单和导航项目 1.使用VS创建一个Ionic空项目&#xff0c;同时创建一个Ionic SideMenu和Ionic Tabs项目。将SideMenu和Tabs项目里的templates和js文件合并到空项目里&#xff0c;修改js对应的代码即可。完整项目工程如下&#xff1a; 2.App.js代码修改如下&#xf…

最佳适应算法和最坏适应算法_算法:好,坏和丑陋

最佳适应算法和最坏适应算法by Evaristo Caraballo通过Evaristo Caraballo 算法&#xff1a;好&#xff0c;坏和丑陋 (Algorithms: The Good, The Bad and The Ugly) Who has been in Free Code Camp without having the experience of spending hours trying to solve Algori…

mysql条件触发器实例_mysql触发器实例一则

例子&#xff0c;实例学习mysql触发器的用法。一&#xff0c;准备二张测试表&#xff1a;1&#xff0c;测试表1复制代码 代码示例:DROP TABLE IF EXISTS test;CREATE TABLE test (id bigint(11) unsigned NOT NULL AUTO_INCREMENT,name varchar(100) NOT NUL…

阿里大数据神预测 胜率仅5.9%中国却1:0胜韩国

写在最前面&#xff1a;这是早晨偶然看到的一篇文章&#xff0c;是对昨天中国却1&#xff1a;0胜韩国的评论。有朋友感慨&#xff1a;努力不放弃的时候&#xff0c;全世界都会帮你。这篇内容很全面的串起阿里巴巴在大数据预测方面的动作&#xff0c;角度很别致&#xff0c;分享…

Python中类、对象与self详解

先介绍一下python中的类与对象/实例。然后详细说明self。说明&#xff1a;对象等同实例&#xff0c;本文称呼不一致时请自行统一 【一】类与对象/实例 1、类 &#xff08;1&#xff09;类由名称、属性、方法三部分组成 &#xff08;2&#xff09;类是抽象模板&#xff0c;比如学…

面试题28 字符串排列

题目描述 输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。 结果请按字母顺序输出。 输入描述: 输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。 1 cla…