AQS(一)

Java并发编程核心在于java.concurrent.util包而juc当中的大多数同步器
实现都是围绕着共同的基础行为,比如等待队列、条件队列、独占获取、共享获
取等,而这个行为的抽象就是基于AbstractQueuedSynchronizer简称AQS,AQS定
义了一套多线程访问共享资源的同步器框架,是一个依赖状态(state)的同步器。

AQS具备的特性:阻塞等待队列,共享/独占, 公平/非公平,可重入,允许中断

例如Java.concurrent.util当中同步器的实现如Lock,Latch,Barrier等,都是基于AQS框架实现:
一般通过定义内部类Sync继承AQS,
将同步器所有调用都映射到Sync对应的方法.
AQS内部维护属性volatile int state (32位)
state:表示资源的可用状态
State三种访问方式:
getState()、setState()、compareAndSetState() .
AQS定义两种资源共享方式 :
Exclusive-独占,只有一个线程能执行,如ReentrantLock .
Share-共享,多个线程可以同时执行,如Semaphore/CountDownLatch.
AQS定义两种队列
同步等待队列 ,条件等待队列

  • 不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只
    需要实现共享资源state的获取与释放方式即可
    ,至于具体线程等待队列的维护
    (如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器 实现时主要实现以下几种方法:

    isHeldExclusively():该线程是否正在独占资源。只有用到 condition才需要去实现它。

    tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败 则返回false。
            
    tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败 则返回false。
            
    tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败; 0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
           
    tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许 唤醒后续等待结点返回true,否则返回false。
    

同步队列

在这里插入图片描述

ReentrantLock加锁和释放锁的底层原理

好了,那么现在如果有一个线程过来尝试用ReentrantLock的lock()方法进行加锁,会发生什么事情呢?
很简单,这个AQS对象内部有一个核心的变量叫做state,是int类型的,代表了加锁的状态。初始状态下 ,这个state的值是0。
另外,这个AQS内部还有一个关键变量,用来记录当前加锁的是哪个线程,初始化状态下,这个变量是null。

在这里插入图片描述

 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;
        /**
         *  节点在等待队列中,节点的线程等待在Condition上,当其他线程对Condition调用了signal()方法后,
         *  该节点会从等待队列中转移到同步队列中,加入到同步状态的获取中
         */
        static final int CONDITION = -2;
        /**
         * 表示下一次共享式同步状态获取将会被无条件地传播下去
         */
        static final int PROPAGATE = -3;

        /**
         * 标记当前节点的信号量状态 (1,0,-1,-2,-3)5种状态
         * 使用CAS更改状态,volatile保证线程可见性,高并发场景下,
         * 即被一个线程修改后,状态会立马让其他线程可见。
         */
        volatile int waitStatus;

        /**
         * 前驱节点,当前节点加入到同步队列中被设置
         */
        volatile Node prev;

        /**
         * 后继节点
         */
        volatile Node next;

        /**
         * 节点同步状态的线程
         */
        volatile Thread thread;

        /**
         * 等待队列中的后继节点,如果当前节点是共享的,那么这个字段是一个SHARED常量,
         * 也就是说节点类型(独占和共享)和等待队列中的后继节点共用同一个字段。
         */
        Node nextWaiter;

        /**
         * Returns true if node is waiting in shared mode.
         */
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

接着线程1跑过来调用ReentrantLock的lock()方法尝试进行加锁,这个加锁的过程,直接就是用CAS操作将state值从0变为1。
如果之前没人加过锁,那么state的值肯定是0,此时线程1就可以加锁成功。
一旦线程1加锁成功了之后,就可以设置当前加锁线程是自己。所以大家看下面的图,就是线程1跑过来加锁的一个过程。
在这里插入图片描述

这个ReentrantLock之所以用Reentrant打头,意思就是他是一个可重入锁。
可重入锁的意思,就是你可以对一个ReentrantLock对象多次执行lock()加锁和unlock()释放锁,也就是可以对一个锁加多次,叫做可重入加锁。
其实每次线程1可重入加锁一次,会判断一下当前加锁线程就是自己,那么他自己就可以可重入多次加锁,每次加锁就是把state的值给累加1,别的没啥变化。

接着,如果线程1加锁了之后,线程2跑过来加锁会怎么样呢?
我们来看看锁的互斥是如何实现的?线程2跑过来一下看到,哎呀!state的值不是0啊?所以CAS操作将state从0变为1的过程会失败,因为state的值当前为1,说明已经有人加锁了!
接着,线程2会将自己放入AQS中的一个等待队列,因为自己尝试加锁失败了,此时就要将自己放入队列中来等待,等待线程1释放锁之后,自己就可以重新尝试加锁了
所以大家可以看到,AQS是如此的核心!AQS内部还有一个等待队列,专门放那些加锁失败的线程!
在这里插入图片描述
接着,线程1在执行完自己的业务逻辑代码之后,就会释放锁!他释放锁的过程非常的简单,就是将AQS内的state变量的值递减1,如果state值为0,则彻底释放锁,会将“加锁线程”变量也设置为null!

接下来,会从等待队列的队头唤醒线程2重新尝试加锁。
好!线程2现在就重新尝试加锁,这时还是用CAS操作将state从0变为1,此时就会成功,成功之后代表加锁成功,就会将state设置为1。
此外,还要把“加锁线程”设置为线程2自己,同时线程2自己就从等待队列中出队了。
最后再来一张图,大家来看看这个过程。
在这里插入图片描述
问题又来了 ,这些是如何进入阻塞队列呢?
首先看下刚开始·创建节点的时候
在这里插入图片描述

/**
     * 节点加入CLH同步队列
     */
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                //队列为空需要初始化,创建空的头节点
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                //set尾部节点
                if (compareAndSetTail(t, node)) {//当前节点置为尾部
                    t.next = node; //前驱节点的next指针指向当前节点
                    return t;
                }
            }
        }
    }
    

在这里插入图片描述

=========================================================================
来深入几个点:1.线程是如何被阻塞的

    /**
     * 阻塞当前节点,返回当前Thread的中断状态
     * LockSupport.park 底层实现逻辑调用系统内核功能 pthread_mutex_lock 阻塞线程
     */
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);//阻塞
        return Thread.interrupted();
    }

本质上Unsafe魔术类中的park()和unpark()去进行线程阻塞和唤醒的

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值