Java之AQS简述

AQS简述

AbstractQueuedSynchronizer 抽象同步队列简称 AQS ,它是实现同步器的 基 础组件,
并发包中锁的底层就是使用 AQS 实现的.
AQS提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架,AQS为一系列同步器依赖于一个单独的原子变量(state)的同步器提供了一个非常有用的基础。子类们必须定义改变state变量的protected方法,这些方法定义了state是如何被获取或释放的。鉴于此,本类中的其他方法执行所有的排队和阻塞机制。子类也可以维护其他的state变量,但是为了保证同步,必须原子地操作这些变量
Java之AQS简述_第1张图片

AbstractQueuedSynchronizer中对state的操作是原子的,且不能被继承。所有的同步机制的实现均依赖于对改变量的原子操作。为了实现不同的同步机制,我们需要创建一个非共有的(non-public internal)扩展了AQS类的内部辅助类来实现相应的同步逻辑。AbstractQueuedSynchronizer并不实现任何同步接口,它提供了一些可以被具体实现类直接调用的一些原子操作方法来重写相应的同步逻辑。AQS同时提供了互斥模式(exclusive)和共享模式(shared)两种不同的同步逻辑。一般情况下,子类只需要根据需求实现其中一种模式,当然也有同时实现两种模式的同步类,如ReadWriteLock。

  • state状态
    AbstractQueuedSynchronizer维护了一个volatile int类型的变量,用户表示当前同步状态。volatile虽然不能保证操作的原子性,但是保证了当前变量state的可见性
    Java之AQS简述_第2张图片
    3个方法
    protected final int getState() {
        return state;
    }

  
    protected final void setState(int newState) {
        state = newState;
    }

   
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

这三种叫做均是原子操作,其中compareAndSetState的实现依赖于Unsafe的compareAndSwapInt()方法

资源共享方式

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

//独占方式。尝试获取资源,成功则返回true,失败则返回false
 protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
//独占方式。尝试释放资源,成功则返回true,失败则返回false
 protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }
//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源
 protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
    }
 //共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false
protected boolean tryReleaseShared(int arg) {
        throw new UnsupportedOperationException();
    }
//该线程是否正在独占资源。只有用到condition才需要去实现它
 protected boolean isHeldExclusively() {
        throw new UnsupportedOperationException();
    }
  • AQS 是一个 FIFO 的双向队列,其内部通过节点 head 和 tail 记录队
    首和队尾元素,队列元素的类型为 Node 。 其中 Node 中的 thread 变量用来存放进入 AQS
    队列里面的线程: Node 节点内部的 SHARED 用来标记该线程是获取共享资源时被阻塞
    挂起后放入 AQS 队列的, EXCLUSIVE 用来标记线程是获取独占资源时被挂起后放入
    AQS 队列的 ; waitStatus 记录当前线程等待状态,可以为 CANCELLED (线程被取消了)、
    SIGNAL ( 线程需要被唤醒)、 CONDITION (线程在条件队列里面等待〉、 PROPAGATE (释
    放共享资源时需要通知其他节点〕; prev 记录当前节点的前驱节点, next 记录当前节点的
    后继节点 。
   private transient volatile Node head;

    private transient volatile Node tail;


    private volatile int state;
    //Node类
static final class Node {
 		//用来标记该线程是获取共享资源时被阻塞挂起后放入 AQS 队列的
        static final Node SHARED = new Node();
		//用来标记线程是获取独占资源时被挂起后放入AQS 队列的
        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;
		//用来存放进入 AQS队列里面的线程
        volatile Thread thread;

      	
        Node nextWaiter;

        /**
         * Returns true if node is waiting in shared mode.
         */
        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(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }
  • 在 AQS 中 维持了 一 个 单 一 的状态信息 state,可以通过 getState 、 setState 、
    compareAndS etState 函数修改其值 。 对于 Reentran tLock 的 实 现来说, state 可以用 来表示
    当 前线程获取锁的可重入次数 ;对于 读写锁 ReentrantReadWriteLock 来说 , state 的 高 16
    位表示读状态,也就是获取该读锁的次数,低 16 位表示获取到写锁的线程的可重入次数;
    对于 semaphore 来说, state 用来表示当前可用信号的 个数:对于 CountDownlatch 来说,
    state 用 来表示计数器当前的值 。

  • AQS 有个内 部类 ConditionObject , 用来结合锁实现线程同步 。 ConditionObject 可以
    直接访问 AQS 对 象 内部的变量,比如 state 状态值和 AQS 队列。 ConditionObject 是条件
    变量 , 每个条件变量对应 一个 条件队列 (单向链表队列),其用来存放调用条件变量 的
    await 方法后被阻塞的线程

在独占方式下 , 获取与释放资源的流程如下
  public final void acquire(int arg) {
        if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    1. 当 一个线程调用 acquire(int arg) 方法获取独占 资源时,会首先使用 tryAcquire 方
      法尝试获取资源, 具体是设置状态变量 state 的值,成功则直接返回,失败则将当前线程
      封装为类型为 Node.EXCLUSIVE 的 Node 节点后插入到 AQS 阻塞 队列的尾部,并调用
      LockSupport.park(this) 方法挂起自己
public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    1. 当 一个线程调用 release( int arg)方法时会尝试使用 tryRelease 操作释放资源,这里
      是设置状态变量 state 的值,然后调用 LockSupport.unpark(thread)方法激活 AQS 队列里面
      被阻塞的一个线程(thread) 。 被激活的线程则使用 tryAcquire 尝试,看当前状态变量 state
      的值是否能满足自己的需要,满足则该线程被激活,然后继续 向下运行,否则还是会被放
      入 AQS 队列并被挂起

AQS 类并没有提供可用的句Acquire 和町Release 方法,正如 AQS
是锁阻塞和同步器的基础框架一样, t可Acquire 和 tryRelease 需要由具体的子类来实现 。
子类在实现 tryAcquire 和 tryRelease 时要根据具体场景使用 CAS 算法尝试修改 state 状态值,
成功则返 回 true,否则返回 false 。 子类还需要定义,在调用 acquire 和 release 方法时 state
状态值的增减代表什么含义

在共享方式下 ,获取与释放资源的流程如下
public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

当线程调用 acquireShared(int arg) 获取共享资源时,会首先使用 trγAcq山reShared
尝试获取资源 , 具体是设置状态变量 state 的 值,成功则 直接返 回,失败则将当前线
程 封装为类型为 Node . SHARED 的 Node 节 点后插入 到 AQS 阻 塞 队列的尾部,并使用
LockSupport.park(th is) 方法挂起自己。


 public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

当一个线程调用 re leas eS hared( int a电)时会尝试使用 tryRelea seS hared 操作释放资
源,这里是设置状态变量 state 的值,然后使用 LockSupport.unpark ( thread )激活 AQS 队
列里面被阻塞的一个线程 (thread) 。被激活的线程则使用 tryReleaseShared 查看当前状态变
量 state 的值是否能满足自 己的 需要,满足则该线程被撤活,然后继续向下运行,否则还
是会被放入 AQS 队列并被挂起

同样 需要注意的是, AQS 类并没有提供可用的 t可AcquireShared 和 tryReleaseShared
方法,正如 AQS 是锁阻塞和同步器的基础框架一样 , tryAcquireShared 和 tryReleaseShared
需要由具体的子类来实现 。 子类在实现 t叩AcquireShared 和 tryReleaseShared 时要根据具体
场景使用 CAS 算法尝试修改 state 状态值,成功则返回 true ,否则返回 false

入队操作

当 一个线程获取锁失败后该线程会被转换为 No de 节点,然后就会使用
enq(final Node node) 方法将该节点插入到 AQS 的阻塞队列 。

 private Node enq(final Node node) {
        for (;;) {
        //t 指向了尾部节点
            Node t = tail;
            //第一次循环t =null
            if (t == null) { // Must initialize
            //第一次走这里
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
            //第二次之后的循环走这里
                node.prev = t;
                //设置队列尾
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

  • compareAndSetHead使用cas设置头值为new Node() update
 /**
     * CAS head field. Used only by enq.
     */
    private final boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }


Java之AQS简述_第3张图片
图片来源java并发之美

  • compareAndSetTail 使用CAS设置尾部节点为 node 比较原来的尾部节点是不是expect是就设置为新的update
 private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }

Java之AQS简述_第4张图片

图片来源java并发之美

自定义一个简单实现 不可重入锁

state 为 0 表示目前 锁没有被线程持有 , state 为 1 表示锁己经被某一个线程持有, 由于是不可重入锁 ,所以不
需要记录持有锁的线程获取锁的次数**

package com.ghgcn.threadstudy.lesson04;

import java.io.Serializable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @author 刘楠
 * @since 2019/7/2
 */
public class MyLock implements Lock, Serializable {

    //创 建一个Sync来做具体的工作
    private final Sync sync = new Sync();


    @Override
    public void lock() {
        //设置为1去获取锁
        sync.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

        sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        //设置获取锁的过期时间
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override
    public void unlock() {
        sync.release(0);
    }

    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }


    public boolean isLocked() {
        return sync.isHeldExclusively();
    }

    /**
     * 实现队列同步器  内部帮助类
     */
    private static class Sync extends AbstractQueuedSynchronizer {

        /**
         * 如果为0,则尝试获取锁
         *
         * @param acquires
         * @return
         */
        @Override
        protected boolean tryAcquire(int acquires) {

            assert acquires == 1;
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }

            return false;
        }

        /**
         * 尝试设置 acquires为0 ,释放锁
         *
         * @param acquires
         * @return
         */
        @Override
        protected boolean tryRelease(int acquires) {

            assert acquires == 1;
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            //设置占用锁的线程为null
            setExclusiveOwnerThread(null);
            //设置state=0
            setState(0);

            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            //如果为1,说明锁已经被其它线程占用了 ,0则表示锁没有被其它线程占用
            return getState() == 1;
        }


        //提供条件变量接口
        Condition newCondition() {
            return new ConditionObject();
        }
    }

}


定义了 一个 内部类 Sync 用来实现具体的锁的操
作, Sync 则 继承了 AQS 。由于我们 实现的是独占模式的锁,所 以 Sync 重写了 tryAcqui陀 、
tryRe lease 和 isHe ldExclusively 3 个方法。另 外, Sync 提供 了 newCondition 这个方法用来
支持条件变量。

你可能感兴趣的:(JAVA并发)