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:// ...
随机推荐
- yii 获取当前模块名、控制器名 、动作名
yii1 1. 获取控制器名 在控制器中获取控制器名: $name = $this->getId(); 在视图中获取控制器名: $name = Yii::app()->controller ...
- hadoop家族学习路线图之hadoop产品大全
大数据这个词也许几年前你听着还会觉得陌生,但我相信你现在听到hadoop这个词的时候你应该都会觉得“熟悉”!越来越发现身边从事hadoop开发或者是正在学习hadoop的人变多了.作为一个hadoop ...
- oracle schema 白话文详解
概述: (一)什么Oracle叫用户(user): A user is a name defined in the database that can connect to and access ob ...
- webpack4构建react脚手架
create-react-app 脚手架还没有更新到webpack4,但是猛然间发现webpack4已经到 v4.12.0 版本了!!!慌得不行,正好端午有空所以研究了一波,自己搭建了一个简单的rea ...
- [转载]Linux下关于system调用
曾经的曾经,被system()函数折磨过,之所以这样,是因为对system()函数了解不够深入.只是简单的知道用这个函数执行一个系统命令,这远远不够,它的返回值.它所执行命令的返回值以及命令执行失败原 ...
- java web程序 jdbc连接数据库错误排查方法
学习jsp.我遇到了麻烦,我总是看不懂500错误,因为每次都显示整个页面的错误,都是英文 我看不懂,后来,把他弄烦了,我也烦了,比起学习java.那个异常可以很简单的就知道.现在解决 了第一个问题,5 ...
- 如果安装提示缺少某vistal c++ 装完仍然报错,可以尝试
https://www.lfd.uci.edu/~gohlke/pythonlibs/#mysql-python 从这个网站直接下载需要的包进行安装 来源 https://www.cnblogs.co ...
- [转]JSON.stringify 详解
来自:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify J ...
- 通过优化在UE4中实现良好性能和高质量视觉效果
转自:http://gad.qq.com/program/translateview/7160166 译者:赵菁菁(轩语轩缘) 审校:李笑达(DDBC4747) 对于任何追求UE4性能最佳.同时又想 ...
- Oracle函数中将参数放在模糊查询中
--diagnosis_name like '%'||diagnosis_names||'%' create or replace function asdf(MIN_DATE IN varchar2 ...