5.Lock接口及其实现ReentrantLock
jdk1.7.0_79
在java.util.concurrent.locks这个包中定义了和synchronized不一样的锁,重入锁——ReentrantLock,读写锁——ReadWriteLock等。在已经有了内置锁synchronized的情况下,为什么又出现了Lock显示锁呢?本文将以Lock作为Java并发包源码解读的开始.
Lock定义最基本的加锁和解锁操作。
|
Lock |
|
|
void lock(); |
阻塞方式获取锁,直到获取锁后才返回 |
|
void locklnterruptibly(); |
获取锁,除非当前线程被中断 |
|
Condition newCondition(); |
返回一个Condition实例绑定到这个锁实例 |
|
boolean tryLock(); |
不管是否获取到锁,都立即返回,非阻塞 |
|
boolean tryLock(long time, TimeUnit unit); |
在一定时间内阻塞获取锁 |
|
void unlock(); |
释放锁 |
Lock接口有一个实现类——重入锁ReentrantLock。进入ReentrantLock类中我们就发现它对于Lock接口的实现基本上都借助于一个抽象静态内部类Sync,该内部类继承自AbstractQueuedSynchronizer,接着又发现两个静态内部类NonfairSync、FairSync,这两个静态内部类又是继承自刚刚的Sync。这里就要引入两个新的概念了——公平锁与非公平锁。在公平的锁上,线程将按照它们发出请求的顺序来获得锁,但在非公平的锁上,则允许“插队”:当一个线程请求非公平的锁时,如果在发出请求的同时该锁的状态变为可用,那么这个线程将跳过队列中所有的等待线程并获得这个锁。(《Java并发编程实战》)
ReentrantLock你可以称之为重入锁(递归锁)、显示锁、排他锁(独占锁),显示锁很好理解,即线程在获取锁和释放锁的时候都需要代码显示操作。重入锁是什么概念呢?synchronized实际上也是可重入锁,意思就是一个线程已经持有这个锁,当这个线程再次获得这个锁的时候不会被阻塞,而是使其同步状态计数器+1,这样做的目的当然就是为了防止死锁,当线程释放这个锁的时候,同步状态计数器-1,直到递减至0才表示这个锁完全释放完毕,其他线程可以获取。那什么又是排他锁呢?我们直到AQS定义了两种模式下获取锁与释放锁的操作,那就是独占模式和共享模式,所谓独占模式就是只有一个线程能持有这个锁,而共享模式则是这个锁可以由多个线程所持有。例如ReebtrabtReadWriteLock的读锁就能由多个线程所持有。在知道了ReentrantLock的特性之后,我们再来看它其内部实现。
在前两节解析AQS的时候我们就提到,AQS所提供的同步器是实现锁的基础框架,固然ReentrantLock同样也是基于AQS,而ReentrantLock并没有直接实现AQS抽象类,而是将在其内部定义一个Sync内部类来聚合AQS,这样聚合而不是继承的目的是为了将锁的具体实现与锁的使用做一个隔离,锁的使用者关心的是锁如何才能被正确使用,而锁的实现者关心的是锁如何基于AQS被正确的实现。先讨论ReentrantLock$Sync抽象内部类。在讨论前先回顾一下能够被子类重写的AQS方法有哪些:
|
AbstractQueuedSynchronizer |
|
|
protected boolean tryAcquire(int arg) |
子类可实现在独占模式下获取同步状态的具体方法。 |
|
protected boolean tryRelease(int arg) |
子类可实现在独占模式下释放同步状态的具体方法。 |
|
protected int tryAcquireShared(int arg) |
子类可实现在共享模式下获取同步状态的具体方法。 |
|
protected int tryReleaseShared() |
子类可实现在共享模式下释放同步状态的具体方法。 |
|
protected boolean isHeldExclusively() |
当前同步器是否在独占模式下被线程占用,一般表示该方法是否被当前线程所独占。 |
通过查看ReentrantLock$Sync的源码可知,Sync一共对AQS重写了这么几个方法:
protected final boolean tryRelease(int release)
protected final boolean isHeldExclusively()
为什么Sync只重写了这两个方法呢?实际上在ReentrantLock的内部还有另外两个内部类NonfairSync非公平锁和FairSync公平锁,这两个内部类是Sync的具体实现,很显然能够得出,对于锁的获取非公平锁和公平锁的实现是不一样的,而对于锁的释放两者均是相同实现。针对ReentrantLock的非公平锁和公平锁接下来我们来一一探讨他们的不同点和相同点。
在ReentrantLock定义了一个成员变量——sync,并且他提供了两个构造方法,其默认无参构造方法创建的是非公平锁,而有参的构造方法则传入一个boolean类型来决定构造一个公平锁还是非公平锁。
public class ReentrantLock implements Lock {
private final Sync sync;
public ReentrantLock() {
sync = new NofairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
……
}
1.lock()
针对开篇提到的Lock接口定义的方法,我们先来看ReentrantLock对Lock#lock的实现:
public class ReentrantLock implements Lock {
……
public void lock() {
sync.lock();
}
……
}
这个方法是抽象内部类定义的一个抽象方法,从命名可以看到这个类实际上就是AQS的acquire获取锁的具体实现,在这里我们能看到非公平锁和公平锁对获取锁的不同实现,我们先来看非公平锁对Sync#lock的实现:
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquire) {//在ReentrantLock$NonFairLock才终于看到了对AbstractQueuedSynchronizer#tryAcquire的具体实现。
return nonfairTryAcquire(acquires);//而tryAcquire的实现实际上又是在其父类ReentrantLock$Lock中实现的,好像有点绕,一会子类实现,一会父类实现。可以先这么来理解,既然它把tryAcquire的具体实现又定义在了父类,那说明这一定是父类对公共方法的抽取(Extract Method),其他地方一定有用到nonfairTryAcquire方法,不然JDK的作者不会闲的蛋疼。
}
}
ReentrantLock$Sync中非公平锁的获取锁的实现
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$NonFairLock非公平锁的lock阻塞获取锁的实现,在调用非公平锁的lock方法时,就会首先进行“抢锁”操作,也就是compareAndSetState这个方法是利用的CAS底层方法看能否抢占到锁,而不是按照先后获取获取锁的方式放到同步队列中获取锁,公平锁就是这样。既然说到了公平锁获取锁的方式,我们不妨和ReentrantLock$FairLock作一个对比:
static final class FairSync extends Sync {
inal void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {//在ReentrantLock$NonFairLock我们看到它老老实实的实现了AQS定义的tryAcquire方法,而没有调用父类的方法,从这里我们也基本能推断在ReentrantLock中没有其他地方会引用到这个方法。
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;
}
}
从公平锁(nonfairTryAcquire)和非公平锁(tryAcquire)的两个方法对比可知:公平锁在获取锁的时候首先会判断当前线程是否有前驱节点已经在队列中等待,如果有返回true,则不进行获取锁的操作,这一点就是和公平锁最大的区别,只有当前线程没有前驱节点才获取锁。ReentrantLock默认构造非公平锁,而实际上用得最多的也是非公平锁,公平锁从一定程度上能防止“饥饿”,但非公平锁在性能上却优于公平锁,我们做以下试验得知的确如此(详细试验过程《【试验局】ReentrantLock中非公平锁与公平锁的性能测试》)
2.lockInterruptibly()
这个方法和lock方法的区别就是,lock会一直阻塞下去直到获取到锁,而lockInterruptibly则不一样,它可以响应中断而停止阻塞返回。ReentrantLock对其的实现是调用的Sync的父类AbstractQueuedSynchronizer#acquireInterruptibly方法:
//ReentrantLock#lockInterruptibly
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);//因为ReentrantLock是排它锁,故调用AQS的acquireInterruptibly方法
}
//AbstractQueuedSynchronizer#acquireInterruptibly
public final void acquireInterruptibly(int arg) throws InterruptedException{
if (Thread.interrupted()) //线程是否被中断,中断则抛出中断异常,并停止阻塞
throw new InterruptedException;
if (!tryAcquire(arg)) //首先还是获取锁,具体参照上文
doAcquireInterruptibly(arg);//独占模式下中断获取同步状态
}
通过查看doAcquireInterruptibly的方法实现不难发现它和acquireQueued大同小异,前者抛出异常,后者返回boolean。具体实在不再讨论,参照源码以及《2.从AbstractQueuedSynchronizer(AQS)说起(1)——独占模式的锁获取与释放》。
3.tryLock()
此方法为非阻塞式的获取锁,不管有没有获取锁都返回一个boolean值。
//ReentrantLock#tryLock
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
可以看到它实际调用了Sync#nonfairTryAcquire非公平锁获取锁的方法,这个方法我们在上文lock()方法非公平锁获取锁的时候有提到,而且还特地强调了该方法不是在NonfairSync实现,而是在Sync中实现很有可能这个方法是一个公共方法,果然在非阻塞获取锁的时候调用的是此方法。详细解析参照上文。
4.tryLock(long timeout, TimeUnit unit)
此方法是表示在超时时间内获取到同步状态则返回true,获取不到则返回false。由此可以联想到AQS的tryAcquireNanos(int arg, long nanosTimeOut)方法
//ReentrantLock#tryLock
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
果然Sync实际上调用了父类AQS的tryAcquireNanos方法。
//AbstractQueuedSynchronizer#tryAcquireNanos
public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();//可以看到前面和lockInterruptibly一样
return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout);//首先也会先尝试获取锁
}
在doAcquireNanos实际也和acquireQueued、doAcquireInterruptibly差不多,不同的是增加了超时判断。
关于Lock和ReentrantLock介绍到这里,在AQS和这里遗留了一个问题——Condition,在下一节中单独介绍Condition。
5.Lock接口及其实现ReentrantLock的更多相关文章
- jdk1.5多线程Lock接口及Condition接口
jdk1.5多线程的实现的方式: jdk1.5之前对锁的操作是隐式的 synchronized(对象) //获取锁 { } //释放锁 jdk1.5锁的操作是显示的:在包java.util.concu ...
- synchronized关键字,Lock接口以及可重入锁ReentrantLock
多线程环境下,必须考虑线程同步的问题,这是因为多个线程同时访问变量或者资源时会有线程争用,比如A线程读取了一个变量,B线程也读取了这个变量,然后他们同时对这个变量做了修改,写回到内存中,由于是同时做修 ...
- Java多线程的~~~Lock接口和ReentrantLock使用
在多线程开发.除了synchronized这个keyword外,我们还通过Lock接口来实现这样的效果.由Lock接口来实现 这样的多线程加锁效果的优点是非常的灵活,我们不在须要对整个函数加锁,并且能 ...
- 线程同步 Lock接口
同步:★★★★★ 好处:解决了线程安全问题. 弊端:相对降低性能,因为判断锁需要消耗资源,产生了死锁. 定义同步是有前提的: 1,必须要有两个或者两个以上的线程,才需要同步. 2,多个线程必须保证使用 ...
- Java基础知识强化之多线程笔记06:Lock接口 (区别于Synchronized块)
1. 简介 我们讲到了如何使用关键字synchronized来实现同步访问.本文我们继续来探讨这个问题,从Java 5之后,在java.util.concurrent.locks包下提供了另外一种方式 ...
- Java多线程(五) Lock接口,ReentranctLock,ReentrantReadWriteLock
在JDK5里面,提供了一个Lock接口.该接口通过底层框架的形式为设计更面向对象.可更加细粒度控制线程代码.更灵活控制线程通信提供了基础.实现Lock接口且使用得比较多的是可重入锁(Reentrant ...
- 多线程里面的关键字,wait, notfiy, 锁(synchronized), lock接口
多线程环境下,必须考虑线程同步的问题,这是因为多个线程同时访问变量或者资源时会有线程争用,比如A线程读取了一个变量,B线程也读取了这个变量,然后他们同时对这个变量做了修改,写回到内存中,由于是同时做修 ...
- java多线程Lock接口简介使用与synchronized对比 多线程下篇(三)
前面的介绍中,对于显式锁的概念进行了简单介绍 显式锁的概念,是基于JDK层面的实现,是接口,通过这个接口可以实现同步访问 而不同于synchronized关键字,他是Java的内置特性,是基于JVM的 ...
- java 锁 Lock接口详解
一:java.util.concurrent.locks包下常用的类与接口(lock是jdk 1.5后新增的) (1)Lock和ReadWriteLock是两大锁的根接口,Lock代表实现类是Reen ...
随机推荐
- 应用程序写Xml文档
主要用到CreateElement.CreateTextNode.CreateComment.AppendChild.InsertAfter方法 代码如下: XmlDocument document ...
- 关于block使用的几点注意事项
1.在使用block前需要对block指针做判空处理. 不判空直接使用,一旦指针为空直接产生崩溃. if (!self.isOnlyNet) { if (succBlock == NULL) { // ...
- 【机器学习】神经网络实现异或(XOR)
注:在吴恩达老师讲的[机器学习]课程中,最开始介绍神经网络的应用时就介绍了含有一个隐藏层的神经网络可以解决异或问题,而这是单层神经网络(也叫感知机)做不到了,当时就觉得非常神奇,之后就一直打算自己实现 ...
- 测试页面,页面里边一次加载50张不同的图片,每张5M以上,查看浏览器的内存使用情况
测试页面 1.需要你写个测试页面,页面里边一次加载50张不同的图片,每张5M,查看浏览器的内存使用情况 2.可以10张 递增的方式测试 3.图片需要缩放,比如所有图片缩放成600*800的比例 目的 ...
- position relative
position的默认值是static,(也就是说对于任意一个元素,如果没有定义它的position属性,那么它的position:static) 如果你想让这个#demo里的一个div#sub相对于 ...
- 商城项目实战 | 2.1 Android 仿京东商城——自定义 Toolbar (一)
前言 本文为菜鸟窝作者刘婷的连载."商城项目实战"系列来聊聊仿"京东淘宝的购物商城"如何实现. 现在很多的 APP 里面都有自己的自定义风格,特别是京东商城中自 ...
- 学习Java之前操作环境的安装及配置
1.根据自己的系统版本下载相应版本的JDK(Java开发运行时环境) 查看自己系统版本的方法:在桌面上右键计算机(win7,win10是此电脑,XP是我的电脑),点击属性,进入到计算机属性页面以后里面 ...
- vue+websocket+express+mongodb实战项目(实时聊天)
继上一个项目用vuejs仿网易云音乐(实现听歌以及搜索功能)后,发现上一个项目单纯用vue的model管理十分混乱,然后我去看了看vuex,打算做一个项目练练手,又不想做一个重复的项目,这次我就放弃颜 ...
- JS + HTml 时钟代码实现
JS+ Html 画布实现的时钟效果图: 闲来无聊做了一个网页的时钟,效果模拟传统时钟的运行方式, 运用了html 的画布实现指针,背景图片引用了网络图片. 具体原理: 首先将时钟分为四个不同区域,对 ...
- 全易通人事考勤工资验厂管理系统软件创建连接SQL2000数据库的操作方法和说明
全易通人事考勤工资验厂管理系统软件创建连接SQL2000数据库的操作方法和说明.全易通人事考勤工资验厂管理系统软件,有2种数据库,一个是ACCESS,另一个是SQL.不过由于ACCESS数据库比较小, ...