• 深入ReentrantLock


    1 ReentrantLock和synchronized的区别

    核心区别:
    ● ReentrantLock是个类,synchronized是关键字,当然都是在JVM层面实现互斥锁的方式
    效率区别:
    ● 如果竞争比较激烈,推荐ReentrantLock去实现,不存在锁升级概念。而synchronized是存在锁升级概念的,如果升级到重
    量级锁,是不存在锁降级的。
    底层实现区别:
    ● 实现原理是不一样,ReentrantLock基于AQS实现的,synchronized是基于ObjectMonitor
    功能向的区别:
    ● ReentrantLock的功能比synchronized更全面。
    ● ReentrantLock支持公平锁和非公平锁
    ● ReentrantLock可以指定等待锁资源的时间。
    选择哪个:如果你对并发编程特别熟练,推荐使用ReentrantLock,功能更丰富。如果掌握的一般般,使用synchronized会更好

    2 AQS概述

            AQS就是AbstractQueuedSynchronizer抽象类,AQS其实就是JUC包下的一个基类,JUC下的很多内容都是基于AQS实现了部分功能,比如ReentrantLock,ThreadPoolExecutor,阻塞队列,CountDownLatch,Semaphore,CyclicBarrier等等都是基于AQS实现。
            首先AQS中提供了一个由volatile修饰,并且采用CAS方式修改的int类型的state变量。
            其次AQS中维护了一个双向链表,有head,有tail,并且每个节点都是Node对象

    1. static final class Node {
    2. static final Node SHARED = new Node();
    3. static final Node EXCLUSIVE = null;
    4. static final int CANCELLED = 1;
    5. static final int SIGNAL = -1;
    6. static final int CONDITION = -2;
    7. static final int PROPAGATE = -3;
    8. volatile int waitStatus;
    9. volatile Node prev;
    10. volatile Node next;
    11. volatile Thread thread;
    12. }

    3 加锁流程源码剖析

    3.1 加锁流程概述

    这个是非公平锁的流程

    3.2 三种加锁源码分析

    3.2.1 lock方法

    执行lock方法后,公平锁和非公平锁的执行套路不一样

    1. // 非公平锁
    2. final void lock() {
    3. // 上来就先基于CAS的方式,尝试将state从0改为1
    4. if (compareAndSetState(0, 1))
    5. // 获取锁资源成功,会将当前线程设置到exclusiveOwnerThread属性,代表是当前线程持有着锁资源
    6. setExclusiveOwnerThread(Thread.currentThread());
    7. else
    8. // 执行acquire,尝试获取锁资源
    9. acquire(1);
    10. }
    11. // 公平锁
    12. final void lock() {
    13. // 执行acquire,尝试获取锁资源
    14. acquire(1);
    15. }

    acquire方法,是公平锁和非公平锁的逻辑一样

    1. public final void acquire(int arg) {
    2. // tryAcquire:再次查看,当前线程是否可以尝试获取锁资源
    3. if (!tryAcquire(arg) &&
    4. // 没有拿到锁资源
    5. // addWaiter(Node.EXCLUSIVE):将当前线程封装为Node节点,插入到AQS的双向链表的结尾
    6. // acquireQueued:查看我是否是第一个排队的节点,如果是可以再次尝试获取锁资源,如果长时间拿不到,挂起线程
    7. // 如果不是第一个排队的额节点,就尝试挂起线程即可
    8. acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    9. // 中断线程的操作
    10. selfInterrupt();
    11. }

    tryAcquire方法竞争锁最资源的逻辑,分为公平锁和非公平锁

    1. // 非公平锁实现
    2. final boolean nonfairTryAcquire(int acquires) {
    3. // 获取当前线程
    4. final Thread current = Thread.currentThread();
    5. // 获取了state熟属性
    6. int c = getState();
    7. // 判断state当前是否为0,之前持有锁的线程释放了锁资源
    8. if (c == 0) {
    9. // 再次抢一波锁资源
    10. if (compareAndSetState(0, acquires)) {
    11. setExclusiveOwnerThread(current);
    12. // 拿锁成功返回true
    13. return true;
    14. }
    15. }
    16. // 不是0,有线程持有着锁资源,如果是,证明是锁重入操作
    17. else if (current == getExclusiveOwnerThread()) {
    18. // 将state + 1
    19. int nextc = c + acquires;
    20. if (nextc < 0) // 说明对重入次数+1后,超过了int正数的取值范围
    21. // 01111111 11111111 11111111 11111111
    22. // 10000000 00000000 00000000 00000000
    23. // 说明重入的次数超过界限了。
    24. throw new Error("Maximum lock count exceeded");
    25. // 正常的将计算结果,复制给state
    26. setState(nextc);
    27. // 锁重入成功
    28. return true;
    29. }
    30. // 返回false
    31. return false;
    32. }
    33. // 公平锁实现
    34. protected final boolean tryAcquire(int acquires) {
    35. // 获取当前线程
    36. final Thread current = Thread.currentThread();
    37. // ....
    38. int c = getState();
    39. if (c == 0) {
    40. // 查看AQS中是否有排队的Node
    41. // 没人排队抢一手 。有人排队,如果我是第一个,也抢一手
    42. if (!hasQueuedPredecessors() &&
    43. // 抢一手~
    44. compareAndSetState(0, acquires)) {
    45. setExclusiveOwnerThread(current);
    46. return true;
    47. }
    48. }
    49. // 锁重入~~~
    50. else if (current == getExclusiveOwnerThread()) {
    51. int nextc = c + acquires;
    52. if (nextc < 0)
    53. throw new Error("Maximum lock count exceeded");
    54. setState(nextc);
    55. return true;
    56. }
    57. return false;
    58. }
    59. // 查看是否有线程在AQS的双向队列中排队
    60. // 返回false,代表没人排队
    61. public final boolean hasQueuedPredecessors() {
    62. // 头尾节点
    63. Node t = tail;
    64. Node h = head;
    65. // s为头结点的next节点
    66. Node s;
    67. // 如果头尾节点相等,证明没有线程排队,直接去抢占锁资源
    68. return h != t &&
    69. // s节点不为null,并且s节点的线程为当前线程(排在第一名的是不是我)
    70. (s == null || s.thread != Thread.currentThread());
    71. }

    addWaite方法,将没有拿到锁资源的线程扔到AQS队列中去排队

    1. // 没有拿到锁资源,过来排队, mode:代表互斥锁
    2. private Node addWaiter(Node mode) {
    3. // 将当前线程封装为Node,
    4. Node node = new Node(Thread.currentThread(), mode);
    5. // 拿到尾结点
    6. Node pred = tail;
    7. // 如果尾结点不为null
    8. if (pred != null) {
    9. // 当前节点的prev指向尾结点
    10. node.prev = pred;
    11. // 以CAS的方式,将当前线程设置为tail节点
    12. if (compareAndSetTail(pred, node)) {
    13. // 将之前的尾结点的next指向当前节点
    14. pred.next = node;
    15. return node;
    16. }
    17. }
    18. // 如果CAS失败,以死循环的方式,保证当前线程的Node一定可以放到AQS队列的末尾
    19. enq(node);
    20. return node;
    21. }
    22. private Node enq(final Node node) {
    23. for (;;) {
    24. // 拿到尾结点
    25. Node t = tail;
    26. // 如果尾结点为空,AQS中一个节点都没有,构建一个伪节点,作为head和tail
    27. if (t == null) {
    28. if (compareAndSetHead(new Node()))
    29. tail = head;
    30. } else {
    31. // 比较熟悉了,以CAS的方式,在AQS中有节点后,插入到AQS队列的末尾
    32. node.prev = t;
    33. if (compareAndSetTail(t, node)) {
    34. t.next = node;
    35. return t;
    36. }
    37. }
    38. }
    39. }

    acquireQueued方法,判断当前线程是否还能再次尝试获取锁资源,如果不能再次获取锁资源,或者又没获取到,尝试将当前线程挂起

    1. // 当前没有拿到锁资源后,并且到AQS排队了之后触发的方法。 中断操作这里不用考虑
    2. final boolean acquireQueued(final Node node, int arg) {
    3. // 不考虑中断
    4. // failed:获取锁资源是否失败(这里简单掌握落地,真正触发的,还是tryLock和lockInterruptibly)
    5. boolean failed = true;
    6. try {
    7. boolean interrupted = false;
    8. // 死循环…………
    9. for (;;) {
    10. // 拿到当前节点的前继节点
    11. final Node p = node.predecessor();
    12. // 前继节点是否是head,如果是head,再次执行tryAcquire尝试获取锁资源。
    13. if (p == head && tryAcquire(arg)) {
    14. // 获取锁资源成功
    15. // 设置头结点为当前获取锁资源成功Node,并且取消thread信息
    16. setHead(node);
    17. // help GC
    18. p.next = null;
    19. // 获取锁失败标识为false
    20. failed = false;
    21. return interrupted;
    22. }
    23. // 没拿到锁资源……
    24. // shouldParkAfterFailedAcquire:基于上一个节点转改来判断当前节点是否能够挂起线程,如果可以返回true
    25. // 如果不能,就返回false,继续下次循环
    26. if (shouldParkAfterFailedAcquire(p, node) &&
    27. // 这里基于Unsafe类的park方法,将当前线程挂起
    28. parkAndCheckInterrupt())
    29. interrupted = true;
    30. }
    31. } finally {
    32. if (failed)
    33. //lock方法中,基本不会执行。
    34. cancelAcquire(node);
    35. }
    36. }
    37. // 获取锁资源成功后,先执行setHead
    38. private void setHead(Node node) {
    39. // 当前节点作为头结点 伪
    40. head = node;
    41. // 头结点不需要线程信息
    42. node.thread = null;
    43. node.prev = null;
    44. }
    45. // 当前Node没有拿到锁资源,或者没有资格竞争锁资源,看一下能否挂起当前线程
    46. private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    47. // -1,SIGNAL状态:代表当前节点的后继节点,可以挂起线程,后续我会唤醒我的后继节点
    48. // 1,CANCELLED状态:代表当前节点以及取消了
    49. int ws = pred.waitStatus;
    50. if (ws == Node.SIGNAL)
    51. // 上一个节点为-1之后,当前节点才可以安心的挂起线程
    52. return true;
    53. if (ws > 0) {
    54. // 如果当前节点的上一个节点是取消状态,我需要往前找到一个状态不为1的Node,作为他的next节点
    55. // 找到状态不为1的节点后,设置一下next和prev
    56. do {
    57. node.prev = pred = pred.prev;
    58. } while (pred.waitStatus > 0);
    59. pred.next = node;
    60. } else {
    61. // 上一个节点的状态不是1或者-1,那就代表节点状态正常,将上一个节点的状态改为-1
    62. compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    63. }
    64. return false;
    65. }

  • 相关阅读:
    API 接口的安全设计验证:ticket,签名,时间戳
    【动态规划】字串中回文的个数
    Java后端开发(九)-- idea(2022版)将commit(未push)的 本地仓库 的 多条commit记录 进行撤销
    Kubernetes kafka系列 | k8s部署kafka+zookeepe集群
    CrossOver 24下载-CrossOver 24 for Mac下载 v24.0.0中文永久版
    tkinter控件样式
    【2022秋招面经】——测试
    AI/AO模拟量数据类型
    高端无主灯设计灯光设计该如何布置射灯灯具?
    Spark 结构化流写入Hudi 实践
  • 原文地址:https://blog.csdn.net/qq_45309297/article/details/134487907