zoukankan      html  css  js  c++  java
  • AQS原理及应用

     To use this class as the basis of a synchronizer, redefine the
     * following methods, as applicable, by inspecting and/or modifying
     * the synchronization state using {@link #getState}, {@link
     * #setState} and/or {@link #compareAndSetState}:
     *
     * <ul>
     * <li> {@link #tryAcquire}
     * <li> {@link #tryRelease}
     * <li> {@link #tryAcquireShared}
     * <li> {@link #tryReleaseShared}
     * <li> {@link #isHeldExclusively}
     * </ul>

     上面这段话是AQS源码的一段注解,意思是使用AQS实现一个同步器的话需要覆盖实现上面li标签中的这些方法,并且使用getState、setState、compareAndSetState这几个方法来对状态进行操作。

    如果你对JDK源码较为熟悉的话,你会发现AQS(AbstractQueuedSynchronizer)是并发过程中很常见的一个抽象类,我们常用的CountDownLatch、ReentrantLock、FutureTask(1.8不再使用AQS)、Semaphore等类都是在内部定义了一个叫做Sync的内部类,而Sync类继承了AQS抽象类并重写了一些必要的方法。可重入锁ReentrantLock中,state可以用来表示当前线程获取锁的可重入次数;对于读写锁ReentrantReadWriteLock来说,state的高16位表示获取到的读锁的线程的可重入次数,低16位是写锁;对于Semaphore来说,state用来表示当前可用信号的个数;对于CountDownlatch来说,state用来表示计数器当前的值

     abstract static class Sync extends AbstractQueuedSynchronizer {
        ...
        ...
        ...
    }

    下面说说AQS到底是用来做什么的:

    AQS抽象类的内部是一个final的Node类,类中定义了一个volatile的整形状态量state,通过这个Node类来建立一个FIFO的线程等待队列(多线程抢占资源失败被阻塞时会进入此队列)。

    static final class Node {
            /** Marker to indicate a node is waiting in shared mode */
            static final Node SHARED = new Node();
            /** Marker to indicate a node is waiting in exclusive mode */
            static final Node EXCLUSIVE = null;
    
            /** waitStatus value to indicate thread has cancelled */
            static final int CANCELLED =  1;
            /** waitStatus value to indicate successor's thread needs unparking */
            static final int SIGNAL    = -1;
            /** waitStatus value to indicate thread is waiting on condition */
            static final int CONDITION = -2;
            /**
             * waitStatus value to indicate the next acquireShared should
             * unconditionally propagate
             */
            static final int PROPAGATE = -3;

    而这个队列中的每个结点对应一个线程,结点内封装了这个线程的基本信息,状态和等待的资源类型等。

         * <p>To enqueue into a CLH lock, you atomically splice it in as new
         * tail. To dequeue, you just set the head field.
         * <pre>
         *      +------+  prev +-----+       +-----+
         * head |      | <---- |     | <---- |     |  tail
         *      +------+       +-----+       +-----+
         * </pre>
         *
         * <p>Insertion into a CLH queue requires only a single atomic
         * operation on "tail", so there is a simple atomic point of
         * demarcation from unqueued to queued. Similarly, dequeuing
         * involves only updating the "head". However, it takes a bit
         * more work for nodes to determine who their successors are,
         * in part to deal with possible cancellation due to timeouts
         * and interrupts.

    这里借用网上的一张图来说明这个队列的框架。

    AQS定义了两种资源共享的方式,独占和共享,Exclusive和Share,前者常用的是ReentrantLock,后者常用的是前面提到的CountDownLatch、Semaphore等。

    继承AQS,实现state标志位的获取、释放方式,即可实现自定义同步器,至于具体的等待队列的维护、阻塞入队、唤醒出队等在AQS中已经实现好了。标志位涉及到的实现函数如下:

    • isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
    • tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
    • tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
    • tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
    • tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

     自定义同步器

    独占锁以ReentrantLock为例,state初始值为0,表示未锁状态,当一个线程执行了lock()方法之后,会调用tryAcquire方法独占该锁并将state+1,此后其他线程尝试获取锁时便会失败,进入队列,知道刚才的线程释放锁将state值更改为0时,其他线程才有机会获取这个独占锁。当然,这个锁时可重入的,获得锁的线程可以继续state+1,不过只有state为0的时候其他线程才会唤醒。

    共享锁以CountDownLatch为例,初始化线程数为n,及将state初始值设置为n,表示可以有n个线程同时获取这个共享锁,当有线程执行一countDown(),state就会CAS的方式减少1,知道所有线程执行完,state值为0的时候,会unpark主线程,然后主线程会从await状态被唤醒返回,继续执行其他指令。

    一般情况自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。需要说明的是AQS也支持同时实现独占同步和共享同步,比如ReentrantReadWriteLock。


    通常,如果一个线程没有拿到锁,便会被封装成结点加入队尾,检查状态之后会调用park方法进入waiting状态,等待unpark方法或interrupt方法唤醒自己,当它被唤醒之后会检查自己是否有资格拿到锁,如果成功,head会指向当前节点,并检查是否被中断过;否则会继续等待。

    AQS是支持中断的,比如acquireInterruptibly方法和acquireSharedInterruptibly方法。下面将书上看到的互斥锁Mutex源码贴在下面供大家研究,它不支持重入,只有0(未锁定)和1(锁定)两种状态:

    class Mutex implements Lock, java.io.Serializable {
        // 自定义同步器
        private static class Sync extends AbstractQueuedSynchronizer {
            // 判断是否锁定状态
            protected boolean isHeldExclusively() {
                return getState() == 1;
            }
    
            // 尝试获取资源,立即返回。成功则返回true,否则false。
            public boolean tryAcquire(int acquires) {
                assert acquires == 1; // 这里限定只能为1个量
                if (compareAndSetState(0, 1)) {//state为0才设置为1,不可重入!
                    setExclusiveOwnerThread(Thread.currentThread());//设置为当前线程独占资源
                    return true;
                }
                return false;
            }
    
            // 尝试释放资源,立即返回。成功则为true,否则false。
            protected boolean tryRelease(int releases) {
                assert releases == 1; // 限定为1个量
                if (getState() == 0)//既然来释放,那肯定就是已占有状态了。只是为了保险,多层判断!
                    throw new IllegalMonitorStateException();
                setExclusiveOwnerThread(null);
                setState(0);//释放资源,放弃占有状态
                return true;
            }
        }
    
        // 真正同步类的实现都依赖继承于AQS的自定义同步器!
        private final Sync sync = new Sync();
    
        //lock<-->acquire。两者语义一样:获取资源,即便等待,直到成功才返回。
        public void lock() {
            sync.acquire(1);
        }
    
        //tryLock<-->tryAcquire。两者语义一样:尝试获取资源,要求立即返回。成功则为true,失败则为false。
        public boolean tryLock() {
            return sync.tryAcquire(1);
        }
    
        //unlock<-->release。两者语文一样:释放资源。
        public void unlock() {
            sync.release(1);
        }
    
        //锁是否占有状态
        public boolean isLocked() {
            return sync.isHeldExclusively();
        }
    }

     

  • 相关阅读:
    微信 JS SDK 的 chooseImage 接口在部分安卓机上容易造成页面刷新
    规约模式Specification Pattern
    ASP.NET Core 1.0基础之日志
    C# 7 新特性-2
    C# 7 新特性-1
    ASP.NET Core 1.0基础之诊断
    ASP.NET Core 1.0基础之依赖注入
    ASP.NET Core 1.0 基础之配置
    ASP.NET Core 1.0基础之静态文件处理
    FreeSql生产环境自动升级数据库解决方案
  • 原文地址:https://www.cnblogs.com/ZoHy/p/11300095.html
Copyright © 2011-2022 走看看