上一章《AQS源码阅读》讲了AQS框架,这次讲讲它的应用类(注意不是子类实现,待会细讲)。
ReentrantLock,顾名思义重入锁,但什么是重入,这个锁到底是怎样的,我们来看看类的注解说明

ReentrantLock与隐式锁synchronized功能相同,但ReentrantLock更具有扩展性。
《锁优化》里提到Java在1.6对隐式锁synchronized做了锁的优化,使其性能与显式锁性能相差无异。所以在两者的选择上,更多的是考虑用法,以及功能上的扩展。


ReentrantLock是线程独占的,不能与其他线程共享。所谓的重入,就是当本线程想再次获得锁,不需要重新申请,它本身就已经锁了,即重入该锁。
为什么会允许锁重入呢?因为该线程已经拥有锁了,不会受其他线程干扰,那么里面的共享变量就不会因为多线程执行造成线程不安全。相当于代码已经在串行执行了,没必要再申请多余的锁了,而是重入当前的锁。

ReentrantLock会提供一个公平锁的模式,如果选择这个模式,会尽量使得获取锁是公平的,先来先得,但不一定严格按顺序。
如果选择了公平锁,性能上会比不使用(默认)低一些。没有一定保证顺序,同时也降性能,所以如果没有特别的要求,尽量使用默认的非公平锁。
现在基于以上的认识,来看看ReentrantLock的基本实现吧。

ReentrantLock概览

ReentrantLock是实现Lock接口的。所以主要的方法就是Lock接口定义的方法,包括lock()、tryLock()、unlock()、newCondition()等。
lock()与tryLock()的区别就是前者会一直等到直到获取锁,后者则是尝试在当时获取锁,不会重复去申请获取。
这个newCondition()感觉比较突兀,看起来完全不了解有什么用,和Lock有什么关系,我们后面再详细了解。

ReentrantLock里面有一个最核心的成员变量,sync。sync的类型就是内部类Sync。它是AQS的子类,也就是说它就是实现ReentrantLock同步的工具。而FairSync和unFairSync则是Sync的子类,封装了是否公平的功能,用于赋值给sync成员变量。

Sync同步器

Sync是继承上文所介绍的AQS,是ReentrantLock里面的NonfairSync和FairSync的父类。
看注解可以知道,Sync用了AQS的state(状态原子值)来标识锁被持有的数量。

在AQS中,tryRelease()是没有定义的,所以在Sync中重写了。

先判断下申请解锁的线程是否独占锁的线程,否则抛出异常退出。
然计算新的state值,用当前state减去releases值。对于state值和releases值到底是多少,这里可以先留个悬念,但大家可以思考下上面注解的定义也可以大概猜出来。
最后判断新state值是否为0,为0则没有线程占用,所以设当前独占线程为空,并且更新state。这里更新state值并不需要用CAS原子操作,因为只有一个线程会占用这个锁,不是这个线程都异常退出了。

AQS中核心的tryAcquire()方法并没有在这里实现,因为子类NonfaiSync和FairSync的实现并不一样。但这里同样需要用到nonfairTryAcquire,所以抽象出来了。但为什么同样需要,暂时不得而知,带着问题后面再看看。
先判断当前锁的state是否为0,为0则表示没人获取,然后通过CAS更新为acquires值(依然不知道值是多少),同时更新当前线程为锁的独占线程。
如果state不为0,则表示有线程已经占有了。但可能占有的线程是当前线程,那么当前的state会加上acquires值。
这里很容易就看出来state就是代表重入的次数!所以上面的谜题就解开了,releases,aquires都是代表每次申请的值,在ReentrantLock肯定都是1,他们的计算总值就是原子值state。
如果state不为0,也不是被当前线程占用,那么返回false获取失败。

NonfairSync

没啥特别的,直接调用Sync的方法。也没做修改。

FairSync

公平锁的同步器。只有当递归调用或者没有其他等待者,再或者他自己本身排第一才能获取锁。
这话比较绕口,大概意思应该是不停地轮询申请锁,直到自己排到队列的第一才能获取。

乍看一看,这个方法基本和父类Sync的nonfairTryAcquire()一样,唯一不同点就是在没有线程占用的时候(state=0),多了个!hasQueuedPredecessors()前置判断。

这个方法用来判断是否队列为空,或者当前线程是否在队列的最前面。
所以公平锁模式下,想要能获取锁,除非自己排在队列的最前面。
综上看,FairSync根本没有调用到nonfairTryAcquire(),为何说子类都需要用到呢?继续留着悬念,后面解答。

@ReservedStackAccess

可以看到上面介绍的tryAcquire()和tryRelease()都有@ReservedStackAccess。这个注解到底有什么用?
查找了下资料,这个是JEP 270添加的新注解。它会保护被注解的方法,通过添加一些额外的空间,防止在多线程运行的时候出现栈溢出。具体看下图

lock()、tryLock()成员函数

ReentrantLock里面的lock()方法是调用成员变量sync的acquire()。
无论是否公平锁都是直接调用AQS的acquire()方法,不过就是各自有tryAcuqire()的重写,即上文所说的内容。
参数1,是透传给tryAcquire()的,所以这里代表是入锁一次的意思。

而tryLock()调用的是成员变量sync的nonfairTryAcquire()。上文说到Sync内部类抽象了这个方法出来,说到子类都会用到,说的正是tryLock()方法需要用到。
所以显而易见的,无论是否公平锁,调用tryLock()都是用的非公平锁的方法。为什么呢?
因为tryLock()的try只是尝试,无论是否公平,对于方法来说没有必要,只是尝试申请的时候能否获取锁而已。

至于其他成员函数,大都是围绕获取线程和队列的状态,没什么特别的,在这里不再赘述,有兴趣的可以看看源码。

总结

回顾下要点

  1. ReentrantLock是一个可重入的锁(被当前占用的线程重入)。
  2. 它有两种模式公平与非公平,通过NonfairSync和FairSync赋值sync成员变量实现。
  3. 两种模式都是AQS的子类,通过重写tryAcquire()区别不同。公平锁多了是否在队列的头的判断。
  4. tryLock()方法没有区分模式,都是一样的。

上文提到的newCondition()还没有涉及到,等后续再起一章节说下这个Condition。


更多技术文章、精彩干货,请关注
博客:zackku.com
微信公众号:Zack说码

Java多线程——ReentrantLock源码阅读的更多相关文章

  1. Java多线程——ReentrantReadWriteLock源码阅读

    之前讲了<AQS源码阅读>和<ReentrantLock源码阅读>,本次将延续阅读下ReentrantReadWriteLock,建议没看过之前两篇文章的,先大概了解下,有些内 ...

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

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

  3. Java多线程框架源码阅读之---ReentrantLock

    ReentrantLock基于Sync内部类来完成锁.Sync有两个不同的子类NonfairSync和FairSync.Sync继承于AbstractQueuedSynchronizer. Reent ...

  4. [Java多线程]-ThreadLocal源码及原理的深入分析

    ThreadLocal<T>类:以空间换时间提供一种多线程更快捷访问变量的方式.这种方式不存在竞争,所以也不存在并发的安全性问题. //-------------------------- ...

  5. java.util.HashSet, java.util.LinkedHashMap, java.util.IdentityHashMap 源码阅读 (JDK 1.8)

    一.java.util.HashSet 1.1 HashSet集成结构 1.2 java.util.HashSet属性 private transient HashMap<E,Object> ...

  6. java.util.HashSet, java.util.LinkedHashMap, java.util.IdentityHashMap 源码阅读 (JDK 1.8.0_111)

    一.java.util.HashSet 1.1 HashSet集成结构 1.2 java.util.HashSet属性 private transient HashMap<E,Object> ...

  7. 【JAVA】HashMap源码阅读

    目录 1.关键的几个static参数 2.内部类定义Node节点 3.成员变量 4.静态方法 5.HashMap的四个构造方法 6.put方法 7.扩容resize方法 8.get方法 9.remov ...

  8. java.nio.Buffer源码阅读

    Java 自从 JDK1.4 起,对各种 I/O 操作使用了 Buffer 和 Channel 技术.这种更接近于操作系统的的底层操作使得 I/O 操作速度得到大幅度提升,下面引用一段<Java ...

  9. Java的Vector源码阅读

    * The {@code Vector} class implements a growable array of * objects. Like an array, it contains comp ...

随机推荐

  1. Install the Active Directory Administration Tools on Windows Server

    安装 Active Directory 管理工具 To manage your directory from an EC2 Windows instance, you need to install ...

  2. 快速排序和归并排序(C语言)

    1.0快速排序算法 (1)分解 (2)递归求解 (3)合并 int partition(int a[],int p,int r) { int i=p,j=r+1; int x=a[p]; int te ...

  3. Tomcat部署时war和war exploded区别及验证

    war和war exploded的区别 在使用IDEA开发项目的时候,部署Tomcat的时候通常会出现下边的情况: 是选择war还是war exploded 这里首先看一下他们两个的区别: war模式 ...

  4. 【poj3415-Common Substrings】sam子串计数

    题意:  给出两个串,问这两个串的所有的子串中(重复出现的,只要是位置不同就算两个子串),长度大于等于k的公共子串有多少个. 题解: 这题好像大神们都用后缀数组做..然而我在sam的题表上看到这题,做 ...

  5. bzoj4240 zkw版

    复习一波zkw树                                                                      很显然最后建出来的图不是单调序列就是一个类似 ...

  6. bzoj 2330 SCOI2011糖果 查分约束系统

    就根据题目中给的约束条件建图就行了 需要注意的是,我们要做的是最长路,因为需要约束每个点都是大于0 那么可以建一个超级源指向所有点,超级源的dis是1,边长为0 那么这样做最长路就可以了 好了我们这么 ...

  7. bootstrap-datetimepicker年视图中endDate设置之后比正常时间提前两个月

    问题 bootstrap-datetimepicker年视图中endDate设置结束时间为2016-08,(即8月之后的日期不能选)而在日历上显示时为2016-06,相差两个月,即6月之后的日期不能选 ...

  8. 会话Cookie

    Cookie分为会话Cookie和本地Cookie两种 之前一直理解的是会话Cookie不在本地文件存储,只存储于内存,而本地Cookie因为设置了expire过期时间需要在本地存储 下面是白帽子讲W ...

  9. algorithm ch2 insertsort

    刚开始看到insertsort,思路就是使用新来的元素与前述已经排好序的元素比较.然后进行插入或者跳到下一次比较. 实现的代码如下: void InsertSort(int *pArray, int ...

  10. MySQL MyISAM优化设置点滴

    先说一点问题:   Mysql中的InnoDB和MyISAM是在使用MySQL中最常用的两个表类型,各有优缺点.两种类型最主要的差别就是 InnoDB 支持事务处理与外键和行级锁.而MyISAM不支持 ...