JDK5并发(2) Locks-ReentrantLock
Java.concurrent.locks(2)-ReentrantLock
@(Base)[JDK, locks, ReentrantLock, AbstractQueuedSynchronizer, AQS]
转载请写明:原文地址
系列文章:
-Java.concurrent.locks(1)-AQS
-Java.concurrent.locks(2)-ReentrantLock
ReentrantLock 顾名思义,可重入的独占锁。该对象与synchronized
关键字有着相同的语义和表现,但是它还具有一些扩展的功能。可重入锁被最近的一个成功lock的线程占有(unlock后释放)。该类有一个重要特性体现在构造器上,构造器接受一个可选参数,是否是公平锁,默认是非公平锁。
公平锁:先来一定先排队,一定先获取锁。非公平锁:不保证上述条件。非公平锁的吞吐量更高(throughout),
可重入:同一个线程可以反复在同一个
ReentrantLock
对象上调用lock()
方法,当然对应的是必须调用相同次unlock()
方法。最大的递归次数是:2147483647。2的31次方-1
首先我们回顾一下上一篇文章Java.concurrent.locks(1)-AQS,底层实现了等待队列,我们只需要利用一个原子操作来更改内部状态表示是否锁住了,也就是最核心的tryAcquire()
方法即可。
我们首先来看ReentrantLock的内部结构:
ReentrantLock
--> Sync
--> NonfairSync
--> FairSync
==> lock
==> lockInterruptibly
==> tryLock
==> tryLock
==> unlock
==> newCondition
==> hasQueuedThreads
==> hasQueuedThread
==> hasWaiters
==> getWaitQueueLength
==> toString
==> getWaitingThreads
首先-->
表示内部类,==>
表示方法,我们可清楚看到ReentrantLock
有内部有3个同步器,Sync
继承了AQS的基础同步器,一些公共方法都在这里,NonfairSync
就是非公平的同步器,FairSync
就是公平的同步器。
简单解释一下,当
ReentrantLock
构造器传入true
,那么底层就使用FairSync
,如果传入false
,那么底层就使用NonfairSync
看到这里读者肯定会非常疑惑,到底怎么写的代码就是公平的,不是底层都用的AQS框架吗,底层不是就是一个链表在排队吗。为什么就不公平了呢。别着急,继续往下看。
Summary
其实看到这里,ReentrantLock
我们可以看到两个重点,第一个是,如何实现可重入的。第二个是,怎么样的代码就是公平的,怎么样又是不公平的。所以下面我们就围绕这两个方面进行介绍。
Reentrant
可重入 前文已经解释了,同一个线程可以反复获取已经获取了的ReentrantLock
对象。根据我们对AQS框架的了解,在子类中我们只需要实现一个tryAcquire
函数即可,这一点也是我们反复强调的。我们首先来看下,ReentrantLock
的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;
}
这段代码两个if
就说明了问题。
- 如果当前控制状态(c)是0,表示没有人占有,那么我们设置他为占有,并且把独占线程设为自己。
- 如果当前控制状态不是0,说明有人正在占有,别急,看看这个人是不是自己。如果是自己,那么把控制状态继续累加。
看到这里似乎明白,如果有累加,那么释放的时候就是一个累减的过程,直到减到0,那么这个Lock才算释放,原来可重入的是这个意思!
Fair
公平,首先我们想了解的是,为什么会不公平。我们需要回到AQS的代码:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt(); //
}
我们来回顾一下这个步骤:
- 调用子类抢占状态,如果成功,直接返回
- 如果失败,那么当前线程入队之后,
park
住 - 处理中断异常
考虑如下情况:
- 当前锁被线程A占用
- 此时B挂起在队列中
- C进入方法acquire
此时,恰好A释放,按理应该是队列中的B获取锁,这个符合先到先得的“公平策略”,但是B这是还挂起在,C就开始抢占资源,然后占有了之后直接退出acquire
函数。
这里有个特殊的地方在于,A线程释放锁的最后一步,是唤醒队列第一个元素。参考
release()
方法。这时B唤醒之后,结果无锁可用,这时B又会继续park
。所以在入队那边的操作是一个循环操作
从上面的现象来看,这就是我们不做任何措施的情况下,默认就是非公平锁。不要小看哦,这个确实能够提高这个锁的吞吐量哦,在一些关键场景下还是能发挥一定作用的!
我们下面仔细看一看非公平锁的实现:
// 本方法在Lock.Sync中
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
// 本方法在Lock.UnFairSync中
/**
* Performs non-fair tryLock. tryAcquire is
* implemented in subclasses, but both need nonfair
* try for trylock method.
*/
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;
}
我们需要注意的是这个实现真心非常巧妙。下面简单描述一下调用顺序:
- 外部调用
ReentrantLock.lock()
ReentrantLock
调用内部基类Sync.lock()
,也就是上文的方法1。- 内部的基类
Sync.lock()
如果判断没过,就调用AQS - AQS就调用
UnfairSync.tryAcquire()
其他步骤都很好说,唯独在调用AQS之前多了一步if
操作,也就是上面代码中的方法1。显然本类的作者非常清楚不公平的同步器就是为了提高吞吐量,并且也清楚占用一个锁,无非两步,第一设置控制状态,第二设置独占线程。
所以他就很豪迈地直接进来就先抢一次,抢到了,直接退出,没抢到再继续。给吞吐量一个更多的想像空间。
如何公平?
在AQS类的头部注释中写的非常明白:
Because checks in acquire are invoked before enqueuing, a newly acquiring thread may barge ahead of others that are blocked and queued. However, you can, if desired, define tryAcquire and/or tryAcquireShared to disable barging by internally invoking one or more of the inspection methods, thereby providing a fair FIFO acquisition order. In particular, most fair synchronizers can define tryAcquire to return false if hasQueuedPredecessors (a method specifically designed to be used by fair synchronizers) returns true. Other variations are possible.
我们只需要在tryAcquire()
方法中调用该类定义的protected
方法hasQueuedPredecessors()
来优先判断队列中是否存在等待的人即可。
下面我们看ReentrantLock中公平锁的tryAcquire()
方法的实现:
/**
* 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;
}
果然如注释所写,公平的类只做了一件事情,就是在设置状态的时候检查一下队列中是否还有人在等待。
Summary
在本篇文章中着重介绍了ReentrantLock基于AQS框架的实现。着重介绍了两个特点,第一是可重入,第二是公平与否。在后续的文章中,我们将继续围绕ReentrantLock和他的小伙伴Condition进行一定的介绍。
JDK5并发(2) Locks-ReentrantLock的更多相关文章
- java并发编程——通过ReentrantLock,Condition实现银行存取款
java.util.concurrent.locks包为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器.该框架允许更灵活地使用锁和条件,但以更难用的语法为代价. Lock 接口 ...
- JDK5并发(1) Locks-AQS
AbstractQueuedSynchronizer @(Base)[JDK, locks, ReentrantLock, AbstractQueuedSynchronizer, AQS] 转载请写明 ...
- 并发编程(ReentrantLock&&同步模式之顺序控制)
4.13 ReentrantLock 相对于 synchronized 它具备如下特点 可中断 可以设置超时时间 可以设置为公平锁 支持多个条件变量,即对与不满足条件的线程可以放到不同的集合中等待 与 ...
- java中的 java.util.concurrent.locks.ReentrantLock类中的lockInterruptibly()方法介绍
在java的 java.util.concurrent.locks包中,ReentrantLock类实现了lock接口,lock接口用于加锁和解锁限制,加锁后必须释放锁,其他的线程才能进入到里面执行, ...
- java中的 java.util.concurrent.locks.ReentrantLock类的使用方式
实现了lock的类为:ReentrantLock 接口的方式解释: lock()方法为获取锁对象,如果未获取到锁就一直获取锁. trylock():为布尔值,返回是否获取到了锁,如果没有获取到锁则返回 ...
- Java并发系列[5]----ReentrantLock源码分析
在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可 ...
- Java并发编程基础-ReentrantLock的机制
同步锁: 我们知道,锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源,在Lock接口出现之前,Java应用程序只能依靠synchronized关键字来实现同步锁 ...
- 高并发编程之ReentrantLock
上文学习jvm提供的同步方法synchronized的用法,一些常见的业务类型以及一道以前阿里的面试题,从中学习到了一些并发编程的一些规则以及建议,本文主要学习jdk提供的同步方法reentrantL ...
- Java并发——synchronized和ReentrantLock的联系与区别
0 前言 本文通过使用synchronized以及Lock分别完成"生产消费场景",再引出两种锁机制的关系和区别,以及一些关于锁的知识点. 本文原创,转载请注明出处:http:// ...
随机推荐
- GPU驱动兼容性问题
GPU驱动兼容性问题 问题描述: 将笔记本的GTX860M 的驱动升级到了376.09版本,出现登陆界面,输入密码后黑屏. 解决思路: 由于正常显示登陆窗口,且可以输入密码,基本排除硬件问题和集成显卡 ...
- springcould
[Spring For All 社区周报] 「社区活动」(送书哦)Spring For All 第 1 期高手 QA 环节 — Spring Cloud 微服务实战http://spring4all ...
- Openwrt 3g模块
支持Huawei E367 一.编译选项的选择 都选上 都选上 Network目录下 Utiles Luci 二.USB连接3G模块时,显示如下,表示成功 三.没找到:
- Mac 配置多jdk 随意切换
1下载安装 jdk6:https://support.apple.com/kb/DL1572?locale=zh_CN 2配置环境变量 open .bash_profile export PATH=$ ...
- SpringMVC 实现返回一段数据 & 实现自动发送json格式数据 - AJAX
实现返回一段数据 - AJAX 当页面通过AJAX来访问Controller时,期望得到的不是一个页面而是一段数据,此时可以使用如下方法,直接向相应中写入数据: /** * 直接向响应中写出数据,通常 ...
- ubuntu solute two different terminals cmd
sudo update-alternatives --config x-terminal-emulator select 1. gnome-xterminal
- [UE4]目标是Pawn、Get Player Character
“目标是Pawn”表示这一个定义继承与Pawn类的方法. 这样可以很清楚的看到这个是方法是在什么地方定义的 “Get Player Character”可以获得当前控制的角色实例,可以转换成真正具体的 ...
- phpexcel导入数据出现PHPExcel_RichText Object解决办法
在导入excel的时候会出现异常情况,有的问题出现PHPExcel_RichText object,错误代码如下 PHPExcel_RichText Object ( [_richTextElemen ...
- AVL树Python实现(使用递推实现添加与删除)
# coding=utf-8 # AVL树的Python实现(树的节点中包含了指向父节点的指针) def get_height(node): return node.height if node el ...
- .NET MVC ToList() 转Json
#region 方法一 #region ToList()转json /// <summary> /// 通过类别 Id 获相应产品 /// </summary> /// < ...