JAVA并发(2)-ReentrantLock的见解
上节,我们讲了AQS的阻塞与释放实现原理,线程间通信(Condition)的原理。这次,我们就讲讲基于AQS实现的ReentrantLock(重入锁)。
1. 介绍
结合上面的ReentrantLock类图,ReentrantLock实现了Lock接口,它的内部类Sync继承自AQS,绝大部分使用AQS的子类需要自定义的方法存在Sync中。而ReentrantLock有公平与非公平的区别,即'是否先阻塞就先获取资源',它的主要实现就是FairSync与NonfairSync,后面会从源码角度看看它们的区别。
2. 源码剖析
Sync是ReentrantLock控制同步的基础。它的子类分为了公平与非公平。使用AQS的state代表获取锁的数量
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
abstract void lock();
...
}
我们可以看出内部类Sync是一个抽象类,继承它的子类(FairSync与NonfairSync)需要实现抽象方法lock。
下面我们先从非公平锁的角度来看看获取资源与释放资源的原理
故事就从就两个变量开始:
// 获取一个非公平的独占锁
/**
* public ReentrantLock() {
* sync = new ReentrantLock.NonfairSync();
* }
*/
private Lock lock = new ReentrantLock();
// 获取条件变量
private Condition condition = lock.newCondition();
2.1 上锁(获取资源)
lock.lock()
public void lock() {
sync.lock();
}
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
// 获取资源
final void lock() {
// 若此时没有线程获取到资源,直接设置当前线程独占访问资源。
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// AQS的方法
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
// 实现在父类Sync中
return nonfairTryAcquire(acquires);
}
}
AQS的acquire
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
上一节已经剖析过AQS的acquire的整个流程了,就差子类如何去实现tryAcquire了。
// Sync实现的非公平的tryAcquire
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;
}
尝试获取资源的过程是非常简单的,这里再贴一下acquire的流程图
2.2 释放资源
lock.unlock();
public void unlock() {
// AQS的方法
sync.release(1);
}
AQS的release
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
release的流程已经剖析过了,接下来看看tryRelease的实现
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;
}
tryRelease的实现也很简单,这里再贴一下release的流程图
2.3 公平锁与非公平锁的区别
公平锁与非公平锁,即'是否先阻塞就先获取资源', ReentrantLock中公平与否的控制就在tryAcquire中。下面我们看看,公平锁的tryAcquire
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// (2.3.1)
// sync queue中是否存在前驱结点
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;
}
}
区别在代码(2.3.1)
hasQueuedPredecessors
判断当前线程的前面有无其他线程排队;若当前线程在队列头部或者队列为空返回false
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
3. 总结
本文介绍了利用AQS实现的锁ReetrantLock
- 讲解了tryRelease与tryAcquire的实现原理
- 说了说锁的公平与否的实现,是否在意当前线程是否有其他线程排队
JAVA并发(2)-ReentrantLock的见解的更多相关文章
- Java并发编程-ReentrantLock
代码示例: Lock lock = new ReentrantLock(); lock.lock(); try { // update object state } finally { lock.un ...
- Java并发编程-ReentrantLock源码分析
一.前言 在分析了 AbstractQueuedSynchronier 源码后,接着分析ReentrantLock源码,其实在 AbstractQueuedSynchronizer 的分析中,已经提到 ...
- Java并发编程 ReentrantLock 源码分析
ReentrantLock 一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大. 这个类主要基于AQS(Abst ...
- Java并发基础框架AbstractQueuedSynchronizer初探(ReentrantLock的实现分析)
AbstractQueuedSynchronizer是实现Java并发类库的一个基础框架,Java中的各种锁(RenentrantLock, ReentrantReadWriteLock)以及同步工具 ...
- 【Java并发编程实战】-----“J.U.C”:ReentrantLock之三unlock方法分析
前篇博客LZ已经分析了ReentrantLock的lock()实现过程,我们了解到lock实现机制有公平锁和非公平锁,两者的主要区别在于公平锁要按照CLH队列等待获取锁,而非公平锁无视CLH队列直接获 ...
- 【Java并发编程实战】-----“J.U.C”:ReentrantLock之一简介
注:由于要介绍ReentrantLock的东西太多了,免得各位客官看累,所以分三篇博客来阐述.本篇博客介绍ReentrantLock基本内容,后两篇博客从源码级别分别阐述ReentrantLock的l ...
- Java并发编程总结3——AQS、ReentrantLock、ReentrantReadWriteLock(转)
本文内容主要总结自<Java并发编程的艺术>第5章——Java中的锁. 一.AQS AbstractQueuedSynchronizer(简称AQS),队列同步器,是用来构建锁或者其他同步 ...
- Java并发系列[5]----ReentrantLock源码分析
在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可 ...
- java并发编程——通过ReentrantLock,Condition实现银行存取款
java.util.concurrent.locks包为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器.该框架允许更灵活地使用锁和条件,但以更难用的语法为代价. Lock 接口 ...
随机推荐
- 错误提示:Access denied for user 'GC'@'localhost' (using password: YES)
错误描述:使用的是C3P0连接池 Spring整合Mybatis时出现错误 java.sql.SQLException: Access denied for user 'GC'@'localhost' ...
- 国产mcu理论数据评估工作记录
目录 前言 简要工作记录 前言 时间:20210315 主要记录这两天对国内各IC厂商的 MCU 了解记录. 大环境,ST 厂商 MCU 疯狂涨价,国内 MCU 也越来越完善,还便宜.同时,全球缺晶圆 ...
- Maven项目中resources配置总结
目录 背景 第一部分 基本配置介绍 第二部分 具体配置和注意事项 第三部分 读取resources资源 参考文献及资料 背景 通常Maven项目的文件目录结构如下: # Maven项目的标准目录结构 ...
- Android 之 使用 Intent 在活动间传递数据
•前言 继上次学习了<通过 Intent 完成点击按钮实现页面跳转>后,我们知道了如何通过 Intent 实现页面跳转: Intent 除了可以实现页面跳转外,还可以在跳转的时候传递数据: ...
- MyBatis-Plus Generator自定义模板
相信大家在开发过程中,应该都用过Mybatis-Plus的Generator,但是可能没有自定义过模板并使用. 每个项目都应该有一个从Controller层到Mapper层的通用模板,来去掉哪些简单的 ...
- [leetcode] 单调栈
本文总结单调栈算法. 原问题 学习一个算法,我们需要清楚的是:这个算法最原始的问题背景是什么样的? 下一个更小元素 给定一个数组 nums,返回每个元素的下一个更小的元素的下标 res,即 res[i ...
- Python使用sql语句对mysql数据库多条件模糊查询
1 def find_worldByName(c_name,continent): 2 print(c_name) 3 print(continent) 4 sql = " SELECT * ...
- 【JVM进阶之路】十:JVM调优总结
1.调优原则 JVM调优听起来很高大上,但是要认识到,JVM调优应该是Java性能优化的最后一颗子弹. 比较认可廖雪峰老师的观点,要认识到JVM调优不是常规手段,性能问题一般第一选择是优化程序,最后的 ...
- linux下Mysql 8.0.19 编译安装
1 前言 linux下安装MySQL的方式有很多种,包括以仓库的方式安装(yum,apt,zypper),以包的方式安装(rpm,deb),以docker方式安装,从压缩包解压安装,从源码编译安装,这 ...
- 7.bug生命周期
new:测试发现并提交bug,状态为new/active; open: 分配bug到开发人员,状态为open: fixed:开发人员处理完bug,将状态改为fixed: closed/reopen:测 ...