精华内容
下载资源
问答
  • AQS

    2020-08-02 12:19:59
    AQS

    一、为什么需要AQS?以及AQS的作用和重要性?

    AQS(AbstractQueuedSynchronizer)的重要性

    AQS被用在ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch、ThreadPoolExcutor的Worker中都有运用(JDK1.8)。AQS是这些类的底层原理,JUC包里很多重要的工具类背后都离不开AQS框架。

    目的是为了理解器背后的原理,学习设计思想。

    我们先从宏观的角度去解读AQS,比如为什么需要AQS?AQS有什么作用。

    AQS 作用是一个用于构建锁、同步器等线程协作工具类的框架,利用AQS可以很方便的实现线程协作工具类。而且AQS被广泛应用在JUC包中

    很多用于线程协作的工具类就都可以很方便的被写出来

    可以让更上层的开发极大的减少工作量,避免重复早轮子。

    避免了上层因处理不当而导致的线程安全问题,AQS把这些都做好了。

    二、AQS内部原理解析

    AQS最核心的三个部分作为重点:

    状态、队列(FIFO队列)、期望协作工具类去实现的获取/释放等重要方法

    1、state状态

    private volatile int state;

    state根据具体实现类的作用不同而表示不同的含义。

    例如:在信号量里面,state表示的是剩余许可证的数量

    如果我们最开始把state设置为10,这就代表许可证初始一共有10个,然后当某一个线程取走一个许可证之后,这个state就会变为9,所以信号量的state相当于是一个内部计数器。

    例如:在CountDownLatch工具类里面,state表示的是需要“倒数”的数量

    一开始我们假设把它设置为5,当每次调用CountDown方法时,state就会减1,一致减到0的时候就代表这个门闩被放开。

    例如:state在ReentrantLock中的含义

    在ReentrantLock中它表示的是锁的占有情况

    最开始0,表示没有任何线程占有锁,如果state变成1,则就代表这个锁已经被某一个线程所持有了。

    为什么会往上加呢?

    因为ReentrantLock是可重入的,同一个线程不用释放锁,可以再次拥有这把锁就叫重入,如果这个锁被同一个线程多次获取,那么state就会逐渐的往上加,state的值标识重入的次数。

    state状态

    state修改,因为state是会被多个线程共享的,会被并发地修改,所以去修改的state的方法都必须要保证state是线程安全的,可是state本身它仅仅被volatile修饰的,volatile本身并不足以保证线程安全。volatile的作用,当基本类型的变量进行直接赋值时,如果加了volatile就可以保证它的线程安全,这个就是setstate方法,线程安全的原因。

    在AQS中有一个属性是state,它会被并发修改,它代表当前工具类的某种状态,在不同类总代表不同的含义。

    2、FIFO队列

    FIFO队列,即先进先出队列

    这个队列最主要的作用是存储等待的线程

    假设很多线程都想要同时强锁,那么大部分的线程是抢不到的,就需要有一个对了来存放、管理它们,所以AQS的一大功能就是充当线程的“排队管理器”。

    这个队列是双向链表的形式,其数据结构看似简单,但是想要维护成一个线程安全的双向队列却非常复杂。

    因为要考虑很多的线程并发问题。

    3、获取/释放方法

    获取和释放相关的重要方法,这些方法是协作工具类的逻辑的具体体现

    需要每一个协作用具类自己去实现。所以在不同的工具类中,它们的实现和含义各不相同。

    获取方法,获取操作通常会依赖state变量的值,根据state值不同,协作工具类也会有不同的逻辑

    并且在获取的时候也会经常阻塞。

    总结:

    state 是一个数值,在不同的类中表示不同的含义,往往代表一种状态;

    队列,该队列用来存放线程;

    “获取、释放”的相关方法,需要利用AQS的工具类根据自己的逻辑去实现。

    三、AQS在CountDownLatch类中的应用原理?

    1、AQS用法

    2、AQS在CountDownLatch的应用

    3、总结

    1、AQS用法,利用AQS类的主要步骤

    1)新建一个自己的线程协作工具类

    在内部写一个Sync类

    该Sync类继承AbstractQueuedSynchronizer 即AQS。

    2)想好设计的线程协作工具类的协作逻辑

    在Sync类里根据是否是独占,来重写对应的方法

    如果是独占,则重写tryAcquire和tryRelease等方法

    如果是非独占,则重写tryAcquireShared和tryReleaseShared等方法

    3)在自己的线程协作工具类中实现获取/释放的相关方法

    并在里面调用AQS对应的方法

    独占则调用acquire或release等方法

    非独占则调用acquireShared或releaseShared 或acquireShareInterruptibly等方法。

    展开全文
  • aqs

    2020-10-07 16:56:55
    aqs AQS(abstract queue synchronizer).为什么底层是cas+volatile 概念 aqs是一个框架,用来提供一种阻塞锁和一系列依赖等待队列的同步器框架比如reentrantlock,countdownlatch 使用方法是继承aqs并实现它的模版方法...

    aqs

    AQS(abstract queue synchronizer).为什么底层是cas+volatile

    概念

    aqs是一个框架,用来提供一种阻塞锁和一系列依赖等待队列的同步器框架比如reentrantlock,countdownlatch

    使用方法是继承aqs并实现它的模版方法然后讲子类作为组件的内部类

    是java自带的 synchronized以外的锁机制

    接口

    Lock接口,api

    • lock
    • trylock
    • unlock

    实现lock接口的有很多类,比如reentrantlock,readwritelock等

    问题

    由于这些lock锁机制不是使用syn关键字实现的,那么如何使用竞争锁呢?

    结构

    它本身没有实现任何同步的接口,只定义了同步状态的获取以及释放来提供自定义同步

    aqs的功能分为独占以及共享.

    aqs的同步功能依赖内部的队列Node类型(双向链表)

    fairsync(公平锁.所有线程严格按照fifo获取锁),nofairsync(非公平锁,新线程也有机会抢锁),

    队列:

    aqs维护了一个队列,队列的第一位是当前占用线程,后面几位是等待线程,如果当前线程竞争锁失败则将当前线程保存到node中再阻塞该线程当获取锁的线程释放锁后,再从节点中唤醒一个阻塞的节点.

    队列里出队入队都用cas操作,因为有一堆线程来加node尾巴

    源码解析

    reentrantlock首先调用lock方法尝试获取锁,此处为多线程调用,内部实现为cas操作,其他线程都尝试获取锁,但是其中有一条线程一定是获取了锁的

            final void lock() {
                if (compareAndSetState(0, 1))//cas操作
                    setExclusiveOwnerThread(Thread.currentThread());//设置当前获取锁的线程
                else
                    acquire(1);//继续尝试获取锁
            }
    
    //尝试获取锁   
    public final void acquire(int arg) {
      //如果未获取到锁
            if (!tryAcquire(arg) &&
                //将当前线程封装进node中
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                selfInterrupt();
        }
    

    使用cas操作对state赋值

        /**
         * The synchronization state.
         */
        private volatile int state;//设置成volatile线程间可见
    
    • 当state=0时(默认)无锁
    • 当state>0时有线程获取了锁,重入锁允许设置多个锁,于是state会递增,释放锁时也需要释放多次让state变为0
    • 不同的aqs实现(锁),state表达的含义也是不一样的

    nofairtryAcquire

    非公平锁尝试获取锁状态,获取当前线程,获取state的状态,修改state状态为1

    addwaiter

    当tryAcquire失败后,调用addwaiter方法(new Node)封装,当前Thread,与node属性,设置到等待队列中

    enq

    enq内部通过自旋的方式将node节点设置进队列中

    acquireQueued

    此方法内做抢占锁的操作.只有prev节点为head的节点才有机会抢占锁(tryAcquire,否则就挂起线程)

    shouldparkAfterFailedAcquire

    内部进行判断是否需要挂起线程,修改节点的链接状态(前置节点 后置节点)

    parkAndCheckInterrupt

    通过LockSupport.park方法将当前线程挂起到waiting状态,LockSupport使用了线程原语native方法,unsafe里的函数

    锁的释放

        public final boolean release(int arg) {
          //尝试释放锁
            if (tryRelease(arg)) {
              //如果有其他的头节点
                Node h = head;
                if (h != null && h.waitStatus != 0)
                    unparkSuccessor(h);
                return true;
            }
            return false;
        }
    
            protected final boolean tryRelease(int releases) {
              //减去重入锁的次数
                int c = getState() - releases;
                if (Thread.currentThread() != getExclusiveOwnerThread())
                    throw new IllegalMonitorStateException();
                boolean free = false;
              //如果重入次数为0
                if (c == 0) {
                    free = true;
                  //设置当前独占锁为null
              
                    setExclusiveOwnerThread(null);
                }
              //设置锁状态为0,无锁
                setState(c);
                return free;
            }
    
        private void unparkSuccessor(Node node) {
            /*
             * If status is negative (i.e., possibly needing signal) try
             * to clear in anticipation of signalling.  It is OK if this
             * fails or if status is changed by waiting thread.
             */
            int ws = node.waitStatus;
            if (ws < 0)
                compareAndSetWaitStatus(node, ws, 0);
    
            /*
             * Thread to unpark is held in successor, which is normally
             * just the next node.  But if cancelled or apparently null,
             * traverse backwards from tail to find the actual
             * non-cancelled successor.
             */
            Node s = node.next;
            if (s == null || s.waitStatus > 0) {
                s = null;
                for (Node t = tail; t != null && t != node; t = t.prev)
                    if (t.waitStatus <= 0)
                        s = t;
            }
            if (s != null)
              //释放节点
                LockSupport.unpark(s.thread);
        }
    
    

    解释aqs,要解释cas volatile

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 18,544
精华内容 7,417
热门标签
关键字:

aqs