属性

重入锁是基于AQS实现的,它提供了公平锁和非公平锁两个版本的实现。

  1. public class ReentrantLock implements Lock, java.io.Serializable {
  2.  
  3. /***************公平锁和非公平锁*****************/
  4.  
  5. //锁(该类核心)
  6. private final Sync sync;
  7. //非公平锁版本
  8. static final class NonfairSync extends Sync{……}
  9. //公平锁版本
  10. static final class FairSync extends Sync{……}
  11.  
  12. /**
  13. * 默认使用非公平锁
  14. */
  15. public ReentrantLock() {
  16. sync = new NonfairSync();
  17. }
  18.  
  19. /**
  20. * 手动指定公平策略
  21. */
  22. public ReentrantLock(boolean fair) {
  23. sync = fair ? new FairSync() : new NonfairSync();
  24. }
  25.  
  26. }

非公平锁和公平锁两个类都是内部类,而ReentrantLock中只引入Sync类型的sync,面向接口编程。实际使用哪个版本则由构造器决定,默认使用非公平锁,也可以在构造时手动指定。

构造器

  1. /**
  2. * 构造器:默认非公平,等价于ReentrantLock(false)
  3. */
  4. public ReentrantLock() {
  5. //默认使用非公平锁
  6. sync = new NonfairSync();
  7. }
  8.  
  9. /**
  10. * 构造器:使用指定的公平策略
  11. */
  12. public ReentrantLock(boolean fair) {
  13. sync = fair ? new FairSync() : new NonfairSync();
  14. }

加锁(非公平版)-lock

因为默认使用非公平版本,我们先跟踪一下非公平版的lock()源码,看看是如何实现的

  1. public void lock() {
  2. sync.lock();
  3. }
  4.  
  5. //内部类NonfairSync中的lock方法
  6. final void lock() {
  7. //先使用CAS方式【抢占一次】锁。若成功则独占该锁(将AQS的state由0改为1,并将当前线程设置锁拥有者)
  8. if (compareAndSetState(0, 1))
  9. //将当前线程设置为锁拥有者(exclusiveOwnerThread表示“持有锁的线程”)
  10. setExclusiveOwnerThread(Thread.currentThread());
  11. else//失败,则加入等待队列(入队前会先进行一次获取锁操作)
  12. acquire(1);
  13. }
  14.  
  15. //父类AQS类中的acquire方法
  16. public final void acquire(int arg) {
  17. //入队前,此时锁可能已被释放,先尝试一次获取锁的操作。
  18. //获取失败,则将线程包装成节点,加入等待队列尾部,完成后中断线程。
  19. if (!tryAcquire(arg) &&
  20. acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  21. selfInterrupt();
  22. }
  23.  
  24. protected final boolean tryAcquire(int acquires) {
  25. return nonfairTryAcquire(acquires);
  26. }
  27.  
  28. final boolean nonfairTryAcquire(int acquires) {
  29. final Thread current = Thread.currentThread();
  30. int c = getState();
  31. //锁没被线程持有,则竞争该锁,成功则将state由0改为1,并将当前线程标记为锁拥有者
  32. if (c == 0) {
  1. if (compareAndSetState(0, acquires)) {//CAS方式获取锁
  2. //将当前线程标记为锁拥有者
  3. setExclusiveOwnerThread(current);
  4. return true;
  5. }
  6. }//锁已经被持有
  7. //锁已被线程持有,则统计重入次数。
  8. else if (current == getExclusiveOwnerThread()) {
  9. int nextc = c + acquires;//state值+1(传进来的是1)
  10. if (nextc < 0) // overflow
  11. throw new Error("Maximum lock count exceeded");
  12. //修改state【不需要使用CAS来修改state,相当于偏向锁】
  13. setState(nextc);
  14. return true;
  15. }
  16. return false;
  17. }

加锁(公平版)-lock

再来跟踪一下公平版本的lock()方法源码

  1. /**
  2. * 没有抢占操作,直接进入等待队列排队(公平)
  3. */
  4. final void lock() {
  5. acquire(1);
  6. }
  7.  
  8. //父类AQS类中的acquire方法
  9. public final void acquire(int arg) {
  10. //入队前,此时锁可能已被释放,先尝试一次获取锁的操作。
  11. //获取失败,则将线程包装成节点,加入等待队列尾部,完成后中断线程。
  12. if (!tryAcquire(arg) &&
  13. acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  14. selfInterrupt();
  15. }
  16.  
  17. /**
  18. * tryAcquire公平版本。
  19. */
  20. protected final boolean tryAcquire(int acquires) {
  21. final Thread current = Thread.currentThread();
  22. int c = getState();
  23. //锁没被线程持有
  24. if (c == 0) {
  25. //【快捷方式】先判断是否有前驱节点(因为队列是FIFO,前驱等待时间更长,既然公平,就要保证先来先获取)
  26. //若无前驱,则通过CAS操作获取,成功则将state由0改为1,并设置当前线程拥有该锁。
  27. if (!hasQueuedPredecessors() &&
  28. compareAndSetState(0, acquires)) {
  29. setExclusiveOwnerThread(current);
  30. return true;
  31. }
  32. }
  33. //锁已由当前线程持有,则统计重入次数
  34. else if (current == getExclusiveOwnerThread()) {
  35. int nextc = c + acquires;
  36. if (nextc < 0)
  37. throw new Error("Maximum lock count exceeded");
  38. setState(nextc);
  39. return true;
  40. }//锁由其它线程持有
  41. return false;
  42. }

释放锁-unlock

unlock方法实现都是相同的。

  1. public void unlock() {
  2. sync.release(1);
  3. }
  4.  
  5. protected final boolean tryRelease(int releases) {
  6. int c = getState() - releases;
  7. if (Thread.currentThread() != getExclusiveOwnerThread())
  8. throw new IllegalMonitorStateException();
  9. boolean free = false;
  10. //锁状态已为0,则可以释放锁了。(state累加了多少次,就要对应的减多少次,才能把锁解开)
  11. if (c == 0) {
  12. free = true;//释放锁成功
  13. setExclusiveOwnerThread(null);//设置锁拥有者为null
  14. }
  15. //更新state
  16. setState(c);
  17. return free;
  18. }

总结

1.锁的状态变化?

锁由state表示,初始状态为0。线程初次获取到锁,则将state由0改为1,锁拥有者为当前线程。线程重入获取到锁,依旧将state状态加1,每次重入都加1。

退出临界区state就减1,最终直至0,锁释放,锁拥有者为null。

2.非公平锁获取锁和公平获取锁过程的区别?

非公平lock:

①先进行一次CAS抢占获取锁,成功则返回,失败则进入等待队列。

②入队列前,可能此时锁已被释放,先进行一次CAS获取锁,成功则返回。失败将线程封装成节点加入队列尾部,并中断线程。

公平lock:

①直接进入等待队列。

②入队列前,可能此时锁已被释放,先进行一次获取锁操作。过程是:判断是否有前驱节点,如果有前驱,根据FIFO必然前驱更应优先获取锁,因此获取锁失败。若无前驱,则再通过CAS获取锁,成功则返回,失败将线程封装成节点加入队列尾部,并中断线程。

参考:

五月的仓颉 ReentrantLock实现原理深入探究

活在梦里 AQS源码解读

ReentrantLock源码分析的更多相关文章

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

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

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

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

  3. ReentrantLock源码分析--jdk1.8

    JDK1.8 ArrayList源码分析--jdk1.8LinkedList源码分析--jdk1.8HashMap源码分析--jdk1.8AQS源码分析--jdk1.8ReentrantLock源码分 ...

  4. JUC AQS ReentrantLock源码分析

    警告⚠️:本文耗时很长,先做好心理准备,建议PC端浏览器浏览效果更佳. Java的内置锁一直都是备受争议的,在JDK1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6 ...

  5. ReentrantLock 源码分析以及 AQS (一)

    前言 JDK1.5 之后发布了JUC(java.util.concurrent),用于解决多线程并发问题.AQS 是一个特别重要的同步框架,很多同步类都借助于 AQS 实现了对线程同步状态的管理. A ...

  6. JUC之ReentrantLock源码分析

    ReentrantLock:实现了Lock接口,是一个可重入锁,并且支持线程公平竞争和非公平竞争两种模式,默认情况下是非公平模式.ReentrantLock算是synchronized的补充和替代方案 ...

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

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

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

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

  9. Java并发编程 ReentrantLock 源码分析

    ReentrantLock 一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大. 这个类主要基于AQS(Abst ...

  10. concurrent(三)互斥锁ReentrantLock & 源码分析

    参考文档:Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock:http://www.cnblogs.com/skywang12345/p/3496101.html Reentr ...

随机推荐

  1. Windows SDK 8安装失败的绕坑办法

    安装win sdk 8,提示错误:管道正在被关闭. 查看安装log文件,有如下错误: Error 0x800700e8: Failed to write message type to pipe.Er ...

  2. nmap脚本使用总结

    0x00 前言: nmap的基本介绍和基本使用方法,在乌云知识库中已经有人提交过,讲的比较详细,在此文中就不再讲述. 具体链接:http://drops.wooyun.org/tips/2002 本文 ...

  3. js判断是否是移动端自动跳转到wap页面代码

    <script type="text/javascript"> function is_mobile(){ var regex_match=/(nokia|iphone ...

  4. linux学习笔记整理(九)

    第十章 Centos7-系统进程管理本节所讲内容:10.1 进程概述和ps查看进程工具10.2 uptime查看系统负载-top动态管理进程10.3 前后台进程切换- nice进程优先级-实战scre ...

  5. 2.05-random-uesr-proxy

    import urllib.request def proxy_user(): proxy_list = [ {"https":""}, # {"ht ...

  6. Python中关于if __name__=='__main__'的问题

    先举一个简单的例子: 这里有两个.py的文件,a.py和b.py a.py内容为: b.py的内容为: 当执行b.py时结果为: 也就是将a.py文件里的内容执行一边,b.py里的内容执行一边,这显然 ...

  7. php微信生成微信公众号二维码扫描进入公众号带参数

    https://blog.csdn.net/qq_22823581/article/details/80248555 <?php namespace app\api\model; set_tim ...

  8. 日幣匯率 ( Node-Red 爬蟲 )

    https://tutorials.webduino.io/zh-tw/docs/socket/useful/exchange-node-red.html

  9. P3374 【模板】树状数组 1--洛谷luogu

    题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某一个数加上x 2.求出某区间每一个数的和 输入输出格式 输入格式: 第一行包含两个整数N.M,分别表示该数列数字的个数和操作的总个数. ...

  10. 【Codeforces Round 650】Codeforces #334 (Div. 1)

    模拟CF650,ABC三题,RK90 Codeforces 650 A 思路:首先看式子 \(\sqrt{(x_i-x_j)^2+(y_i-y_j)^2}=|x_i-x_j|+|y_i-y_j|\) ...