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. c# list排序的三种实现方式 (转帖)

    用了一段时间的gridview,对gridview实现的排序功能比较好奇,而且利用C#自带的排序方法只能对某一个字段进行排序,今天demo了一下,总结了三种对list排序的方法,并实现动态传递字段名对 ...

  2. POJ2299逆序对模板(树状数组)

    题目:http://poj.org/problem?id=2299 只能相邻两个交换,所以交换一次只会减少一个逆序对.所以交换次数就是逆序对数. ps:原来树状数组还可以记录后边lowbit位的部分和 ...

  3. C# 解析 json Newtonsoft果然强大,代码写的真好

    C# 解析 json JSON(全称为JavaScript Object Notation) 是一种轻量级的数据交换格式.它是基于JavaScript语法标准的一个子集. JSON采用完全独立于语言的 ...

  4. 使用shell/bat脚本调试java程序示例

    一.linux下shell启动java #!/bin/sh JAVA_HOME=/usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0.x86_64/jre # JVM_OPT ...

  5. java 泛型中 T 和 问号(通配符)的区别

    类型本来有:简单类型和复杂类型,引入泛型后把复杂类型分的更细了: 现在List<Object>, List<String>是两种不同的类型;且无继承关系: 泛型的好处如: 开始 ...

  6. Storm存储结果至Redis

      原有的事务支持使用MemcachedState来进行,现在需要将其迁移至Redis,并且需要记录所有key值列表,因为在redis中虽然可以使用keys *操作,但不是被推荐的方式,所以把所有结果 ...

  7. Distill详述「可微图像参数化」:神经网络可视化和风格迁移利器!

    近日,期刊平台 Distill 发布了谷歌研究人员的一篇文章,介绍一个适用于神经网络可视化和风格迁移的强大工具:可微图像参数化.这篇文章从多个方面介绍了该工具. 图像分类神经网络拥有卓越的图像生成能力 ...

  8. Samba 简介

    SMB 代表的是服务器消息块 (Server Message Block),它是用于在 Windows 上共享文件的协议的原始名称. CIFS 代表公共 Internet 文件系统 (Common I ...

  9. Linux常用命令的命名来源

    很多人在学习Linux的时候会疑惑:这么多的Linux名,他们都是怎么被定义的?林纳斯是怎么制定如此花样繁多且数量庞大的命令?今天这篇文章可能会帮你解开疑惑. ## 1. 目录缩写 缩写 | 全称 | ...

  10. mysql Date查询当天、本周,本月,上一个月的数据

      出自:http://www.cnblogs.com/benefitworld/p/5832897.html 今天 select * from 表名 where to_days(时间字段名) = t ...