概述

ReentrantLock,即重入锁,是一个和synchronized关键字等价的,支持线程重入的互斥锁。只是在synchronized已有功能基础上添加了一些扩展功能。

除了支持可中断获取锁、超时获取锁、非阻塞获取锁这些显示锁的常见功能外,ReentrantLock还支持公平锁(synchronized只支持非公平锁)。

下面分析源码时将聚焦重入和公平这两个功能点的实现。

结构总览

重入锁的大体结构如下:

public class ReentrantLock implements Lock, java.io.Serializable {
abstract static class Sync extends AbstractQueuedSynchronizer {}
static final class NonfairSync extends Sync {}
static final class FairSync extends Sync {}
}

Sync类是AQS的子类,而NonfairSync和FairSync是Sync的子类。

公平锁和非公平锁的区别就在于获取锁时候的逻辑略有不同,其他操作都是一样的,因此公用的操作都放在Sync类里,NonfairSync和FairSync里只是实现自己的tryAcquire(int acquires)方法。

AQS里的state在重入锁里代表线程重入的次数,state=1代表重入锁当前已被某个线程独占,这个线程每重入一次,state++。因为state是int型变量,因此重入锁可以重入的最大次数是2^31-1。

重入实现

重入实现其实上边已经提到了,就是利用state状态表示重入次数,我们以非公平锁的代码为例看一下,下面是Sync类里的 nonfairTryAcquire(int acquires)方法 (上面我们说过,Sync类里是存放NonfairSync与FairSync的公用代码,那么这个nonfairTryAcquire方法为什么放到Sync里呢?我们后面会解释,不要急:)

        final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//state为0代表当前锁没有线程持有,则让当前线程持有锁
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果锁被当前线程持有,则把state+acquires(就是1)
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // 如果state<0,说明超过了int最大值,溢出了
throw new Error("Maximum lock count exceeded");
setState(nextc); // 这里不用CAS,因为锁已被当前线程独占
return true;
}
return false;
}

公平实现

公平锁的实现非常简单,其实就是一句代码,我们看一下FairSync类里的 tryAcquire(int acquires) 方法:

        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;
}
    public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}

我们可以发现,这段代码与上面的 nonfairTryAcquire方法就只多了一句代码,而就是这一句代码就实现了公平锁。hasQueuedPredecessors()方法判断同步队列中是否有更早开始等待锁的线程。如果有,则tryAcquire方法直接返回false让当前线程进入同步队列排队。

特殊的tryLock

之前我们在将重入实现的时候说到,Sync类里有个诡异的nonfairTryAcquire方法,听名字是和非公平锁相关的,按道理应该放到NonfairSync类啊。之所以有这么别扭的设计是为了服务tryLock()方法。

看一下ReentrantLock类的tryLock方法的实现:

    public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}

可以看出,不管是公平锁还是非公平锁,调用的都是 nonfairTryAcquire 方法。

为什么这么实现呢?我们可以想一下tryLock() 的语义,tryLock() 要实现的效果是尝试获取一次锁,如果获取失败不阻塞而是直接返回false。如果在公平锁模式下严格按照公平锁的定义来实现这个方法,那么当同步队列中有其他线程等待的时候,tryLock()都不可能获取到锁,只能返回false。

而事实上,当我们调用tryLock()的时候,很多时候应该都是希望尽可能的成功的,而此时要不要让tryLock()的线程严格排队,其实不是那么重要,因此公平锁下tryLock()方法在获取锁时使用非公平获取模式,即可以插队。

那么如果我们在公平锁模式下就希望tryLock()方法获取锁严格排队呢?可以用tryLock(0, TimeUnit.SECONDS),这个方法等效于一个严格排队的tryLock()方法,之所以等效,是因为tryLock(long timeout, TimeUnit unit)的实现是区分公平锁和非公平锁的,在公平锁的模式下,获取锁的操作是严格按同步队列排队等待的。

那么如果我们在公平锁模式下希望 tryLock(long timeout, TimeUnit unit) 不严格排队,表现的像一个支持超时的tryLock()呢?也是有办法的:

 if (lock.tryLock() || lock.tryLock(timeout, unit)) {
...
}

可以这样组合一下,左边的tryLock()有机会插队获取一次锁,如果没获取到,在用tryLock(timeout, unit)做一次可超时的同步队列排队。

总结

有了前面对AQS的理解基础,现在再来看同步组件的实现,就如果快刀切西瓜!所以说,Doug Lea对AQS的设计真的非常巧妙,ReentrantLock没有用多少代码,就实现了一个加强版的synchronizer。

Java显式锁学习总结之四:ReentrantLock源码分析的更多相关文章

  1. Java显式锁学习总结之六:Condition源码分析

    概述 先来回顾一下java中的等待/通知机制 我们有时会遇到这样的场景:线程A执行到某个点的时候,因为某个条件condition不满足,需要线程A暂停:等到线程B修改了条件condition,使con ...

  2. Java显式锁学习总结之五:ReentrantReadWriteLock源码分析

    概述 我们在介绍AbstractQueuedSynchronizer的时候介绍过,AQS支持独占式同步状态获取/释放.共享式同步状态获取/释放两种模式,对应的典型应用分别是ReentrantLock和 ...

  3. Java显式锁学习总结之二:使用AbstractQueuedSynchronizer构建同步组件

    Jdk1.5中包含了并发大神Doug Lea写的并发工具包java.util.concurrent,这个工具包中包含了显示锁和其他的实用同步组件.Doug Lea在构建锁和组件的时候,大多是以队列同步 ...

  4. Java显式锁学习总结之一:概论

    我们都知道在java中,当多个线程需要并发访问共享资源时需要使用同步,我们经常使用的同步方式就是synchronized关键字,事实上,在jdk1.5之前,只有synchronized一种同步方式.而 ...

  5. Java显式锁学习总结之三:AbstractQueuedSynchronizer的实现原理

    概述 上一篇我们讲了AQS的使用,这一篇讲AQS的内部实现原理. 我们前面介绍了,AQS使用一个int变量state表示同步状态,使用一个隐式的FIFO同步队列(隐式队列就是并没有声明这样一个队列,只 ...

  6. Java显式锁

    Java 显式锁. 一.显式锁 什么是显式锁? 由自己手动获取锁,然后手动释放的锁. 有了 synchronized(内置锁) 为什么还要 Lock(显示锁)? 使用 synchronized 关键字 ...

  7. Java并发编程-ReentrantLock源码分析

    一.前言 在分析了 AbstractQueuedSynchronier 源码后,接着分析ReentrantLock源码,其实在 AbstractQueuedSynchronizer 的分析中,已经提到 ...

  8. Java并发编程之ReentrantLock源码分析

    ReentrantLock介绍 从JDK1.5之前,我们都是使用synchronized关键字来对代码块加锁,在JDK1.5引入了ReentrantLock锁.synchronized关键字性能比Re ...

  9. java多线程---ReentrantLock源码分析

    ReentrantLock源码分析 基础知识复习 synchronized和lock的区别 synchronized是非公平锁,无法保证线程按照申请锁的顺序获得锁,而Lock锁提供了可选参数,可以配置 ...

随机推荐

  1. Notes of fwt

    昨天考试由于不会fwt而爆炸,所以今天搞了一下fwt……话说这玩意的普及程度已经很高了.fwt,快速沃尔什变换,可以用于位运算卷积的优化,是一种线性变换,所以就会有许多好的性质(eg:可以直接模,可以 ...

  2. 洛谷P1106 删数问题

    题目描述 键盘输入一个高精度的正整数N,去掉其中任意k个数字后剩下的数字按原左右次序将组成一个新的正整数.编程对给定的N和k,寻找一种方案使得剩下的数字组成的新数最小. 输出应包括所去掉的数字的位置和 ...

  3. [CQOI2009] 中位数 (前缀和)

    [CQOI2009] 中位数 题目描述 给出1~n的一个排列,统计该排列有多少个长度为奇数的连续子序列的中位数是b.中位数是指把所有元素从小到大排列后,位于中间的数. 输入输出格式 输入格式: 第一行 ...

  4. FreeRTOSv9.0.0在STM32F103RCT6上的移植

    1.去官网下载源代码(FreeRTOSv9.0.0.exe) 2.取出Source文件夹,根据单片机和编译器不同,删除不需要的文件,如下图 3.在CORTEX_STM32F103_IAR文件夹中取出P ...

  5. Rabbitmq -- rpc

    一.前言 MQ本身是基于异步的消息处理,前面的示例中所有的生产者(P)将消息发送到RabbitMQ后不会知道消费者(C)处理成功或者失败(甚至连有没有消费者来处理这条消息都不知道).但实际的应用场景中 ...

  6. 修改Tomcat端口

    1.自己的tomcat修改端口号,在tomcat解压缩目录中conf目录下又一个server.xml,将HTTP协议的端口修改为自己想要的即可 <Connector port="808 ...

  7. CF864 E DP 输出路径

    n个物品有Deadline,拿物品需要花费时间,问取得最大价值的方案. 本质是个01背包,先按时间排序,然后把花费的时间作为背包就行了. 主要就是找方案,倒过来找发生转移的就行了. 太菜了真的不会打C ...

  8. 重构改善既有代码设计--重构手法15:Remove Middle Man (移除中间人)

    某个类做了过多的简单委托动作.让客户直接调用受托类. 动机:在Hide Delegate (隐藏委托关系)的“动机”中,谈到了“封装委托对象”的好处.但是这层封装也是要付出代价的,它的代价是:每当客户 ...

  9. System中关于Property的方法

    System类在java.lang包中,所有方法都是静态的,里边有很多对系统的属性和控制方法 System类有三个成员变量:out-标准输出流(默认是控制台),in-标准输入流(默认是键盘),err- ...

  10. 【BZOJ】3238: [Ahoi2013]差异

    [题意]给定长度为n的小写字母字符串,令Ti表示以i开头的后缀,求Σ[Ti+Tj-2*lcp(Ti,Tj)],1<=i<j<=n. [算法]后缀自动机 [题解]Σ(Ti+Tj)只与n ...