同步器--AQS简单分析

一、concurrent包的实现:

     现在CPU内部已经执行原子的CAS操作,CAS通过调用JNI的代码实现的。JNI:Java Native Interface为JAVA本地调用,允许java调用其他语言。​而compareAndSwapInt就是借助C语言来调用CPU底层指令实现原子操作的。

volatile和CAS的相关知识可以看我的博客:https://blog.csdn.net/weixin_40792878/article/details/81263038

​concurrent包的实现:

由于java的CAS同时具有 volatile 读和volatile写的内存语义,因此Java线程之间的通信现在有了下面四种方式:

         1>.A线程写volatile变量,随后B线程读这个volatile变量。

         2>:A线程写volatile变量,随后B线程用CAS更新这个volatile变量。

         3>:A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。

         4>:A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。

volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent包得以实现的基石。如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:

首先,声明共享变量为volatile;然后,使用CAS的原子条件更新来实现线程之间的同步;

同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。

        AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看,concurrent包的实现示意图如下:

这一篇博客主要来讲队列同步器AbstractQueuedSysnchronizer(简称AQS)。

二、队列同步器--AQS的使用

          AQS是一个用来构建锁和其他同步组件的基础框架,使用了一个int成员变量同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。         

private volatile int state;//共享变量,使用volatile修饰保证线程可见性

状态信息通过procted类型的getState,setState,compareAndSetState进行操作(都是原子操作利用volatile和CAS保证)

AQS定义两种资源共享方式:

    Exclusive(独占,只有一个线程能执行,如ReentrantLock)和

    Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)

1. 同步器组件的设计是基于模板方法模式的,一般的使用方式是这样:

  使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放)例如:在ReentrantLock类中定义静态内部类实现继承AQS类,来重写模板方法

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;

    private final Sync sync;

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

}

2.重写同步器指定方法需要使用下面三个方法用来修改同步状态

getState();    // 获取当前同步状态
setState();    //设置当前同步状态
compareAndSetState(); //使用 CAS设置当前状态,该方法能够保证状态设置的原子性

3.同步器可以重写的方法(子类可以重写AQS提供的方法)如下:

   

4.实现自定义同步组件,可以调用AQS提供的模板方法,如下:

    

设计思想

  对于使用者来讲,我们无需关心获取资源失败,线程排队,线程阻塞/唤醒等一系列复杂的实现,这些都在AQS中为我们处理好了。我们只需要负责好自己的那个环节就好,也就是获取/释放共享资源state的姿势T_T。很经典的模板方法设计模式的应用,AQS为我们定义好顶级逻辑的骨架,并提取出公用的线程入队列/出队列,阻塞/唤醒等一系列复杂逻辑的实现,将部分简单的可由使用者决定的操作逻辑延迟到子类中去实现即可。

总结:我们自定义同步器组件

               1.只需要聚合一个继承了AQS的子类,

               2.子类可以重写AQS相关的方法

               3.自定义同步器组件可以调用AQS的模板方法,(这些模板方法实际是会调用你重写的方法),您调用AQS模板方法就可以实现同步的相关功能。

三、队列同步器AQS模板方法的实现原理

        具体网上的也很详细:https://www.cnblogs.com/daydaynobug/p/6752837.html

        这里简单的概述下。

    1. 同步队列实现

      1.1 同步队列中的节点(Node)用来获取同步节点失败的引用,等待状态以及前驱和后继节点,节点的属性和名称,如下是AQS中定义的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;
   
        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;
        }
}

   1.2.同步队列基本结构

         

    同步器遵循FIFO,首节点就是获取同步状态成功的节点,当首节点释放同步状态后,就唤醒后继节点,后继节点将会在获取到同步状态成功后就会将自己设置为首节点。通过compareAndSet来实现节点状态的更换。

  总结:在获取同步节状态时,同步器维护一个同步队列,获取状态失败的线程就会被加入到队列中并在队列中进行自旋;移出队列(或者停止自旋)的添加就是前驱节点为头结点并且,成功获取到同步状态,在释放同步状态时,调用tryRelease方法释放同步状态,然后唤醒头结点的后继节点。

2.共享式同步状态获取和释放

3.独占式超时获取同步状态   

           请参考源码

猜你喜欢

转载自blog.csdn.net/weixin_40792878/article/details/81269614