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函数即可,这一点也是我们反复强调的。我们首先来看下,ReentrantLocktryAcquire是如何实现的。下面是一个非公平的版本(也就是默认版本),我们先不要在意公平两个字,我们的关键是“可重入”。

     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的更多相关文章

  1. java并发编程——通过ReentrantLock,Condition实现银行存取款

         java.util.concurrent.locks包为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器.该框架允许更灵活地使用锁和条件,但以更难用的语法为代价. Lock 接口 ...

  2. JDK5并发(1) Locks-AQS

    AbstractQueuedSynchronizer @(Base)[JDK, locks, ReentrantLock, AbstractQueuedSynchronizer, AQS] 转载请写明 ...

  3. 并发编程(ReentrantLock&&同步模式之顺序控制)

    4.13 ReentrantLock 相对于 synchronized 它具备如下特点 可中断 可以设置超时时间 可以设置为公平锁 支持多个条件变量,即对与不满足条件的线程可以放到不同的集合中等待 与 ...

  4. java中的 java.util.concurrent.locks.ReentrantLock类中的lockInterruptibly()方法介绍

    在java的 java.util.concurrent.locks包中,ReentrantLock类实现了lock接口,lock接口用于加锁和解锁限制,加锁后必须释放锁,其他的线程才能进入到里面执行, ...

  5. java中的 java.util.concurrent.locks.ReentrantLock类的使用方式

    实现了lock的类为:ReentrantLock 接口的方式解释: lock()方法为获取锁对象,如果未获取到锁就一直获取锁. trylock():为布尔值,返回是否获取到了锁,如果没有获取到锁则返回 ...

  6. Java并发系列[5]----ReentrantLock源码分析

    在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可 ...

  7. Java并发编程基础-ReentrantLock的机制

    同步锁: 我们知道,锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源,在Lock接口出现之前,Java应用程序只能依靠synchronized关键字来实现同步锁 ...

  8. 高并发编程之ReentrantLock

    上文学习jvm提供的同步方法synchronized的用法,一些常见的业务类型以及一道以前阿里的面试题,从中学习到了一些并发编程的一些规则以及建议,本文主要学习jdk提供的同步方法reentrantL ...

  9. Java并发——synchronized和ReentrantLock的联系与区别

    0 前言 本文通过使用synchronized以及Lock分别完成"生产消费场景",再引出两种锁机制的关系和区别,以及一些关于锁的知识点. 本文原创,转载请注明出处:http:// ...

随机推荐

  1. Microsoft Dynamics CRM 如何修改域密码

    一.安装IIS6脚本工具,如下图所示: 二.复制iisadmpwd文件夹到AD Server的C:\Windows\SysWOW64\inetsrv文件夹下 三.注册Iisadmpwd目录下的IISp ...

  2. Openwrt 3G模块的添加

    一. 在menuconfig中添加相关驱动 1. Kernel Modules -> USB Support <*> kmod-usb2 <*> kmod-usb-ohc ...

  3. 【python】格式化字符

    格式化字符串总结如下,红色部分是需要掌握部分: 以下几个常用的实例: 1.%s的使用 "%s is the author" %("paulwinflo")> ...

  4. C++将整型数据转换成大端或小端存储顺序

    大端和小端的概念参考之前博客: 大端/小端,高字节/低字节,高地址/低地址,移位运算 昨晚帮导师从指令中恢复图像的时候,导师要我转换成raw格式,也就是记录图像像素的二进制序列,然后反复强调让我注意大 ...

  5. Date类为什么设计为可变的,而不是像String一样?

    首先,不得不承认,这确实是类库设计的一个错误,所以"为什么"进行了这个错误设计并没有意义.但没有事物一诞生就是完美的,我们的Java只是反应的慢了一点,再慢了一点. 更何况,Dat ...

  6. springMVC参数绑定JSON类型的数据

    需求就是: 现在保存一个Student,并且保存Student的friend,一个student会有多个朋友,这里要传递到后台的参数是: var friends = new Array(); var ...

  7. Spring DI - 依赖注入

    1.IOC(DI) - 控制反转(依赖注入) 所谓的IOC称之为控制反转,简单来说就是将对象的创建的权利及对象的生命周期的管理过程交由Spring框架来处理,从此在开发过程中不再需要关注对象的创建和生 ...

  8. 开源框架:Apache的DBUtils框架

    开源框架:Apache的DBUtils框架 Commons DbUtils 1.4 API 开源框架:DBUtils使用详解 Download Apache Commons DbUtils  官方文档

  9. 廖雪峰Java1-1Java入门-java简介

    Java特点: 一种面向对象的跨平台变成语言 以字节码方式运行在虚拟机上 自带功能齐全的类库 非常活跃的开源社区支持 Java优点: 简单.健壮.安全 跨平台,一次编写,到处运行 高度优化的虚拟机 J ...

  10. javascript的密封对象之seal(),isSealed()方法

    EcmaScrip5t中出现了密封对象概念.密封对象不可扩展,而已有的成员的[Configurable]特性被设置为false.也就是说属性和方法是不能删除的.但是是可以修改的. 示例一: var p ...