• AQS原理解析


    显式锁

    Lock 接口

    Lock接口定义方法如下:

    void lock() // 如果锁可用就获得锁,如果锁不可用就阻塞直到锁释放
    void lockInterruptibly() // 和 lock()方法相似, 但阻塞的线程可中断,抛出 java.lang.InterruptedException异常
    boolean tryLock() // 非阻塞获取锁;尝试获取锁,如果成功返回true
    boolean tryLock(long timeout, TimeUnit timeUnit) //带有超时时间的获取锁方法
    void unlock() // 释放锁
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在Lock中,除了定义锁和释放锁方法以外,还提供了尝试取锁以及锁中断功能。这些都是内置锁synchroinzed所没有的功能。所以在选择使用显式锁还是内置锁时,如果有尝试取锁以及锁中断的需求,就使用Lock锁,如果没有这种需求,就使用synchronized锁。

    实现类

    ReentrantLock类:
    表示重入锁,它是唯一一个实现了Lock接口的类。重入锁指的是线程在获得锁之后,再次获取该锁不需要阻塞,而是直接关联一次计数器增加重入次数。即递归调用时,不会锁住自己。

    ReentrantReadWriteLock类:
    重入读写锁,它实现了ReadWriteLock接口,在这个类中维护了两个锁,一个是ReadLock,一个是WriteLock,他们都分别实现了Lock接口。读写锁是一种适合读多写少的场景下解决线程安全问题的工具,基本原则是:读和读不互斥、读和写互斥、写和写互斥。也就是说涉及到影响数据变化的操作都会存在互斥。

    公平锁&&非公平锁

    公平锁就是按顺序去给锁,先到先得,后到后得。非公平锁就是无序获取锁,谁抢上算谁的。

    AQS

    概念

    aqs全称为AbstractQueuedSynchronizer,它提供了一个FIFO队列,可以看成是一个用来实现同步锁以及其他涉及到同步功能的核心组件,常见的有:ReentrantLock、CountDownLatch等。
    AQS是一个抽象类,主要是通过继承的方式来使用,它本身没有实现任何的同步接口,仅仅是定义了同步状态的获取以及释放的方法来提供自定义的同步组件。
    可以这么说,只要搞懂了AQS,那么J.U.C中绝大部分的api都能轻松掌握。

    原理

    AQS提供了独占和共享两个功能。是以双向队列方式实现的,如下图:
    在这里插入图片描述
    AQS队列内部维护的是一个FIFO的双向链表,这种结构的特点是每个数据结构都有两个指针,分别指向直接的后继节点和直接前驱节点。所以双向链表可以从任意一个节点开始很方便的访问前驱和后继。每个Node其实是由线程封装,当线程争抢锁失败后会封装成Node加入到ASQ队列中去。

    节点添加:
    在这里插入图片描述
    新的线程封装成Node节点追加到同步队列中,设置prev节点以及修改当前节点的前置节点的next节点指向自己。
    通过CAS将tail重新指向新的尾部节点:Node获取到尾部节点,然后比较内存中尾部节点是否与获取到Node节点是否一致,一致则自己改为尾部节点,不一致则重新获取尾部节点,直至自己加入到尾部节点。

    节点删除:
    head节点表示获取锁成功的节点,当头结点在释放同步状态时,会唤醒后继节点,如果后继节点获得锁成功,会把自己设置为头结点,节点的变化过程如下:
    在这里插入图片描述
    修改head节点指向下一个获得锁的节点,新的获得锁的节点,将prev的指针指向null。
    这里有一个小的变化,就是设置head节点不需要用CAS,原因是设置head节点是由获得锁的线程来完成的,而同步锁只能由一个线程获得,所以不需要CAS保证,只需要把head节点设置为原首节点的后继节点,并且断开原head节点的next引用即可。

    源码分析

    首先看AQS中定义的FIFO队列中节点对象Node对象:

    static final class Node {
            static final Node SHARED = new Node();
            static final Node EXCLUSIVE = null;
            static final int CANCELLED =  1;
            static final int SIGNAL    = -1;
            static final int CONDITION = -2;
            static final int PROPAGATE = -3;
            volatile int waitStatus;
            volatile Node prev; //前驱节点
            volatile Node next; //后继节点
            volatile Thread thread;//当前线程
            Node nextWaiter; //存储在condition队列中的后继节点
            //是否为共享锁
            final boolean isShared() { 
                return nextWaiter == SHARED;
            }
    
            final Node predecessor() throws NullPointerException {
                Node p = prev;
                if (p == null)
                    throw new NullPointerException();
                else
                    return p;
            }
    
            Node() {    // Used to establish initial head or SHARED marker
            }
            //将线程构造成一个Node,添加到等待队列
            Node(Thread thread, Node mode) {     // Used by addWaiter
                this.nextWaiter = mode;
                this.thread = thread;
            }
            //这个方法会在Condition队列使用,后续单独写一篇文章分析condition
            Node(Thread thread, int waitStatus) { // Used by Condition
                this.waitStatus = waitStatus;
                this.thread = thread;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    可见,Node中定义了前驱节点对象和后置节点对象,以及当前线程对象等信息。

    剩余源码分析,可参考文章:深入分析AQS实现原理

  • 相关阅读:
    据包捕获和分析工具作原理和用途
    解决SSH连接自动断开的问题
    玻色量子对外合作
    CPU型号参数和SAS卡参数说明
    构建空间场景轻应用,Mapmost Alpha来啦【文末赠书(10本)--第一期】
    Leetcode 【136. 只出现一次的数字】
    VMware 桥接模式下本机 PING 不通虚拟机的解决方法
    冒泡排序算法详解之C语言版
    【C++面向对象】5. this指针
    qlib的工作流管理:mlflow机器学习生命周期管理平台
  • 原文地址:https://blog.csdn.net/qq1309664161/article/details/127712104