刨根问底-ReentrantLock

  • 引言
  • 获取锁
  • 释放锁
  • 公平锁与非公平锁
  • ReentrantLock与synchronized的区别

引言

ReentrantLock,可重入锁,是一种递归无阻塞的同步机制。它可以等同于synchronized的使用,但是ReentrantLock提供了比synchronized更强大、灵活的锁机制,可以减少死锁发生的概率。 API介绍如下:

一个可重入的互斥锁定 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义,但功能更强大。ReentrantLock 将由最近成功获得锁定,并且还没有释放该锁定的线程所拥有。当锁定没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁定并返回。如果当前线程已经拥有该锁定,此方法将立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。

ReentrantLock还提供了公平锁也非公平锁的选择,构造方法接受一个可选的公平参数(默认非公平锁),当设置为true时,表示公平锁,否则为非公平锁。公平锁与非公平锁的区别在于公平锁的锁获取是有顺序的。但是公平锁的效率往往没有非公平锁的效率高,在许多线程访问的情况下,公平锁表现出较低的吞吐量

获取锁

一般都是这么使用ReentrantLock获取锁的:

//非公平锁ReentrantLock lock = new ReentrantLock();lock.lock();

lock方法:

public void lock() {sync.lock();
}

Sync为ReentrantLock里面的一个内部类,它继承AQS(AbstractQueuedSynchronizer),它有两个子类:公平锁FairSync和非公平锁NonfairSync。 ReentrantLock里面大部分的功能都是委托给Sync来实现的,同时Sync内部定义了lock()抽象方法由其子类去实现,默认实现了nonfairTryAcquire(int acquires)方法,可以看出它是非公平锁的默认实现方式。下面我们看非公平锁的lock()方法:

final void lock() {//尝试获取锁if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());else//获取失败,调用AQS的acquire(int arg)方法acquire(1);
}

首先会第一次尝试快速获取锁,如果获取失败,则调用acquire(int arg)方法,该方法定义在AQS中,如下:

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

这个方法首先调用tryAcquire(int arg)方法,在AQS中讲述过,tryAcquire(int arg)需要自定义同步组件提供实现,非公平锁实现如下:

protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);
}final boolean nonfairTryAcquire(int acquires) {//当前线程final Thread current = Thread.currentThread();//获取同步状态int c = getState();//state == 0,表示没有该锁处于空闲状态if (c == 0) {//获取锁成功,设置为当前线程所有if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}//线程重入//判断锁持有的线程是否为当前线程else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;
}

该方法主要逻辑:首先判断同步状态state == 0 ?,如果是表示该锁还没有被线程持有,直接通过CAS获取同步状态,如果成功返回true。如果state != 0,则判断当前线程是否为获取锁的线程,如果是则获取锁,成功返回true。成功获取锁的线程再次获取锁,这是增加了同步状态state。

释放锁

获取同步锁后,使用完毕则需要释放锁,ReentrantLock提供了unlock释放锁:

public void unlock() {sync.release(1);
}

unlock内部使用Sync的release(int arg)释放锁,release(int arg)是在AQS中定义的:

public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;
}

与获取同步状态的acquire(int arg)方法相似,释放同步状态的tryRelease(int arg)同样是需要自定义同步组件自己实现:

protected final boolean tryRelease(int releases) {//减掉releasesint c = getState() - releases;//如果释放的不是持有锁的线程,抛出异常if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;//state == 0 表示已经释放完全了,其他线程可以获取同步状态了if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;
}

只有当同步状态彻底释放后该方法才会返回true。当state == 0 时,则将锁持有线程设置为null,free= true,表示释放成功。

公平锁与非公平锁

公平锁与非公平锁的区别在于获取锁的时候是否按照FIFO的顺序来。释放锁不存在公平性和非公平性,上面以非公平锁为例,下面我们来看看公平锁的tryAcquire(int arg):

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

比较非公平锁和公平锁获取同步状态的过程,会发现两者唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:hasQueuedPredecessors(),定义如下:

public final boolean hasQueuedPredecessors() {Node t = tail;  //尾节点Node h = head;  //头节点Node s;//头节点 != 尾节点//同步队列第一个节点不为null//当前线程是同步队列第一个节点return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}

该方法主要做一件事情:主要是判断当前线程是否位于CLH同步队列中的第一个。如果是则返回true,否则返回false。

ReentrantLock与synchronized的区别

  • 与synchronized相比,ReentrantLock提供了更多,更加全面的功能,具备更强的扩展性。例如:时间锁等候,可中断锁等候,锁投票
  • ReentrantLock还提供了条件Condition,对线程的等待、唤醒操作更加详细和灵活,所以在多个条件变量和高度竞争锁的地方,ReentrantLock更加适合
  • ReentrantLock提供了可轮询的锁请求。它会尝试着去获取锁,如果成功则继续,否则可以等到下次运行时处理,而synchronized则一旦进入锁请求要么成功要么阻塞,所以相比synchronized而言,ReentrantLock会不容易产生死锁些
  • ReentrantLock支持更加灵活的同步代码块,但是使用synchronized时,只能在同一个synchronized块结构中获取和释放。注:ReentrantLock的锁释放一定要在finally中处理,否则可能会产生严重的后果。
  • ReentrantLock支持中断处理,且性能较synchronized会好些。

总结:
觉得有用的客官可以点赞、关注下!感谢支持

刨根问底-ReentrantLock相关推荐

  1. 刨根问底-AQS源码解析

    刨根问底-AQS源码解析 CLH同步队列 入队列 出队列 同步状态的获取与释放 独占式同步状态获取 独占式获取响应中断 独占式超时获取 独占式同步状态释放 共享式同步状态获取 共享式同步状态释放 阻塞 ...

  2. ReentrantLock+线程池+同步+线程锁

    1.并发编程三要素? 1)原子性 原子性指的是一个或者多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行. 2)可见性 可见性指多个线程操作一个共享变量时,其中一个线程对变量 ...

  3. ReentrantLock与synchronized

    1.ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的 ...

  4. ReentrantLock实现原理分析

    ReentrantLock主要利用CAS+CLH队列来实现.它支持公平锁和非公平锁,两者的实现类似. CAS:Compare and Swap,比较并交换.CAS有3个操作数:内存值V.预期值A.要修 ...

  5. JUC AQS ReentrantLock源码分析

    Java的内置锁一直都是备受争议的,在JDK 1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6后,进行大量的锁优化策略,但是与Lock相比synchronized还 ...

  6. 通俗易懂的ReentrantLock,不懂你来砍我

    前言 自己开的坑,跪着也要填完,欢迎来到Java并发编程系列第五篇ReentrantLock,文章风格依然是图文并茂,通俗易懂,本文带读者们深入理解ReentrantLock设计思想. 认识下Reen ...

  7. 这篇 ReentrantLock 看不懂,加我我给你发红包

    来自:Java建设者 回答一个问题 在开始本篇文章的内容讲述前,先来回答我一个问题,为什么 JDK 提供一个 synchronized 关键字之后还要提供一个 Lock 锁,这不是多此一举吗?难道 J ...

  8. 从ReentrantLock的实现看AQS的原理及应用

    来自:美团技术团队 AQS作为JUC中构建锁或者其他同步组件的基础框架,应用范围十分广泛,这篇文章会带着大家从可重入锁一点点揭开AQS的神秘面纱. 前言 Java中的大部分同步类(Lock.Semap ...

  9. ReentrantLock中的Condition(等待和唤醒)

    Condition 类的 awiat 方法和 Object 类的 wait 方法等效 Condition 类的 signal 方法和 Object 类的 notify 方法等效 Condition 类 ...

最新文章

  1. 2020牛客多校G[并查集的两种思想,按秩合并+路径压缩]
  2. Rest 微服务工程搭建03——工程重构
  3. 【转载】ERP系统中的存货计价过程
  4. python教程:dict字典常用方法总结,数据解构(解包)
  5. 自学python能干些什么副业-学会python能干嘛 学会python可以做哪些兼职?
  6. Ubuntu18.04安装markdown工具Typora
  7. HTTP Error 404 - File or Directory not found caused by ISAPI filter of Sharepoint
  8. OSG读取Tif格式的高程数据
  9. 教你一键如何更换证件照底色?
  10. 使用阿里云安装基础软件
  11. OpenStack修改Guest用户密码——利用Qemu guest agent实现
  12. Tesseract学习(一)
  13. 数据科学家处理小数据的7个技巧。
  14. SEO快排的行业秘密,原来SEO快排套路这么深
  15. 文件锁定工具IObit Unlocker v1.2.0单文件
  16. 山药多糖/香菇多糖/茯苓多糖/叶酸壳寡糖包封于PLGA纳米粒
  17. Git Pull Failed: CONFLICT (content): Merge conflict in camus-aggregator/camus-admin-web/src/main/web
  18. 黑马程序员Java零基础视频教程笔记-方法
  19. Invalid packaging for parent POM , must be “pom“ but is “jar“
  20. livp图片怎么打开以及怎么转换成jpg格式教程

热门文章

  1. mysql常用基础操作语法(四)--对数据的简单无条件查询及库和表查询【命令行模式】
  2. [系统安全] 二十一.PE数字签名之(中)Signcode、PEView、010Editor、Asn1View工具用法
  3. python 复制并重命名文件_python 复制并重命名文件
  4. sql 拆分字符串, 表值函数f_split的使用
  5. element ui表单必填_element ui判断是否必填添加校验
  6. java web项目 权限管理
  7. 如何区分虚拟网卡和物理网卡?
  8. PDN设计关键点之滤波电容位置
  9. 幕课在线办公项目笔记——day2
  10. earth power oracle,平行世界 | 他「抛弃」绘画从事摄影,将绘画与摄影结合在一起,创造了电影般的震撼场景,邀请我们与他一起穿越黑暗世界的旅程...