自学内容网 自学内容网

秋招突击——7/19——队列同步器AQS学习

引言

  • 学了锁,学了多线程编程,学了线程池,发现很多都提到了AQS,现在来补一下,也是填一下以前留下的坑。
  • 还是跟以前一样,先过一遍基础知识,然后再过一遍相关的面试题

基础知识

简介

  • 队列同步器AbstractQueuedSynchronizer
    • 构建锁和其他同步组建的基础框架

      • 使用int变量表示同步状态,通过内置的队列完成线程排队工作,是实现大部分同步需求的基础
      • 仅仅定义若干同步状态的获取和释放方法,来实现线程同步,没有实现任何接口
      • 支持独占式和共享式获取同步状态
    • 使用方式继承

      • 子类通过继承并实现方法管理同步状态,使用getState(),setState()和compareAndSetState来修改状态

AQS接口和示例

  • AQS的实现采用的是模板模式,使用他对外提供的三类方法来实现一个同步组件。

    • 这里又不知道模板模式了,得去好好学一下了,学习链接
  • 三类方法分别如下

    • 第一类:同步器提供的用于访问和修改同步状态的三个方法
    • 第二类:继承AQS并重写的5个方法
    • 第三类:9个模板方法,用来组合AQS形成自定义的同步组件
      这里给一个自定义的锁的样例mutex
  • 第一步先在同步器中创建一个内部类继承并实现AQS,重写对应5个方法
    在这里插入图片描述

  • 调用9个模板方法,组合对应同步器的逻辑

在这里插入图片描述

第一类:访问和修改同步状态的方法

重写同步器指定的5个方法时,需要使用同步器提供的如下的三个方法来访问和修改同步状态

  • getState:获取当前同步状态
  • setState(int newState):设置当前同步状态
  • CompareAndSetState(int expect,int update):使用CAS设置当前状态,保证状态设置的原子性
第二类,5个重写的方法
  • isHeldExclusively
    • 判定该线程是否正在独享资源,只有用到condition的时候,才需要实现它
  • tryAcquire(int)
    • 独占的方式,尝试获取资源,成功就返回true,失败就返回false
  • tryRelease(int)
    • 独占的方式,尝试释放资源,成功就返回true,失败就返回false
  • tryAcquireShared
    • 共享方式,尝试获取资源
    • 负数表示失败,0表示成功但是没有可用资源剩余,正数表示成功并且有剩余资源
  • tryReleaseShared
    • 共享方式,尝试释放资源
    • 成功返回true,失败返回false
第三类,9个模板方法

模板方法主要分成三类

  • 独占式获取和释放同步状态
  • 共享式获取和释放同步状态
  • 查询同步队列中的等待线程

第一类,独占式获取和释放同步状态

  • void acquire

    • 独占式获取同步状态,获取成功由该方法返回,否则进入同步队列等待
    • 调用重写的tryAcquire方法
  • void acquireInterruptibly

    • 同上,但是支持响应中断
    • 当前线程未获取到同步状态,而进入同步队列中,如果线程被中断,该方法抛出InterruptedException
  • void tryAcquireNanos

    • 同上,但是支持响应中断,而且增加了超时机制
    • 当前线程在超时时间内没有获取到同步状态,就会返回false,否则返回true
  • bool release

    • 独占式释放同步状态
    • 释放同步状态后,将同步队列中的第一个节点唤醒

第二类,共享式获取和释放同步状态

  • void acquireShared

    • 共享式获取同步状态,获取成功由该方法返回,否则进入同步队列等待
    • 与独占式的区别是同一时刻,有多个线程获取同步状态
  • void acquireSharedInterruptibly

    • 同上,但是支持响应中断
    • 当前线程未获取到同步状态,而进入同步队列中,如果线程被中断,该方法抛出InterruptedException
  • void tryAcquireSharedNanos

    • 同上,但是支持响应中断,而且增加了超时机制
    • 当前线程在超时时间内没有获取到同步状态,就会返回false,否则返回true
  • bool releaseShared

    • 共享式释放同步状态
    • 释放同步状态后,将同步队列中的第一个节点唤醒

第三类、获取同步队列中的线程

  • collection getQueueThread
    • 获取等待在同步队列上的线程的集合

这里给一个自定义的锁的样例mutex

  • 第一步先在同步器中创建一个内部类继承并实现AQS,重写对应5个方法
    在这里插入图片描述
  • 调用9个模板方法,组合对应同步器的逻辑

在这里插入图片描述
具体代码

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

public class Mutex implements Lock {

    // 定义一个静态内部类,用来继承AQS
    private static class Sync extends AbstractQueuedSynchronizer{
        // 是否处于占用状态
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        // 当状态为0的时候获取锁
        @Override
        protected boolean tryAcquire(int arg) {
            if(compareAndSetState(0,1)){
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        // 释放锁,将状态设置为0
        @Override
        protected boolean tryRelease(int arg) {
            if(getState() == 0) throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        // 返回一个Condition,每个condition都包含了一个condition队列
        Condition newCondition(){
            return new ConditionObject();
        }
    }

    // 将操作代理到Sync上
    private final Sync sync = new Sync();

    @Override
    public void lock() {
        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(1);
    }

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

总结
基本步骤如下

  • 继承Lock接口
  • 定义一个内部静态类AQS的子类,重写5个重写方法,调用3个状态修改函数实现
  • 使用9个模板方法实现Lock规定的接口方法
    • 将操作代理到AQS子类Sync上,利用Sync的14种方法来实现Lock

队列同步器实现原理

同步队列


队列节点

  • 节点Node是构成同步队列的基础,获取同步状态失败的线程将会被包装成一个节点,加入到同步队列的尾部。
  • 属性
    • waitStatus等待状态
      • cancelled表示线程超时或者中断,从队列中取消等待
      • signal表示后继节点处于等待状态,当前节点释放同步状态会通知后继节点
      • condition表示节点在等待队列中, 节点等待在Condition对象为锁的等待队列上,当其他线程对Condition调用signal方法时,该节点将等待队列转移到同步队列,加入到同步状态的获取中。
      • propagate表示下一个共享式同步状态将会无条件传播下去
    • prev和next:表示同步队列中的前驱节点和后继节点
    • nextWaiter:等待队列中的后继节点
独占式同步获取和释放
  • 获取同步状态时,主要是通过维护一个同步队列实现
    • 获取状态失败的线程会加入到同步队列中,自旋并尝试获取同步状态
    • 释放同步状态,同步器调用tryRelease方法,释放同步状态,唤醒后继节点
共享式同步获取和释放
  • 共享式同步获取实现

    • 调用tryAcquireShared方法,根据返回的int值判定,大于等于零表示获取成功,这个值表示剩余的资源数。
  • 调用releaseShared方法释放同步状态

独占式同步获取和释放
  • 使用理解
    • 强于Synchronized关键字,能够实现在指定时间内获取同步状态,调用同步器地doAcquireNanos实现
    • 支持中断
  • 设置时间过短
    • 如果设置时间过短,会进入快速自旋过程

相关面试题

怎么理解Lock和AQS的关系

  • Lock
    • 面向锁的使用者,定义了使用者和锁的交互接口,隐藏了实现细节
  • AQS
    • 面向锁的开发者,简化了锁的实现,屏蔽了同步状态的管理,线程的排队等操作
  • 二者很好地隔离了使用者和实现者所需要关注的领域

什么是AQS

  • 队列同步器AQS(AbstractQueuedSynchronizer)
    • 用来构建锁和实现其他同步组件的基础框架
    • 使用int成员变量state表示同步状态
    • 通过内置的FIFO队列完成想要获取资源的线程的排队工作
  • 使用方式
    • 通过继承,子类继承同步器并实现抽象方法来管理同步状态。
      • 子类被推荐为自定义同步组建的静态内部类,同步器自身没有实现任何同步接口,仅仅是定义了若干同步状态获取和释放的方法来自定义同步组件
    • 提供3个获取和修改同步状态的安全方法,getstate,setstate,compareAndSetState
    • 同步器同时支持独占式获取同步状态和共享式获取同步状态
    • 典型的应用
      • ReentrantLock、读写锁ReenReadWriteLock等

AQS是怎么实现同步管理的?底层数据结构?

  • 实现方式
    • 使用双向链表和使用volatile修饰的int型变量state
      • state
        • 0 没有线程占用同步资源
        • 大于0 有线程占用同步资源
        • 大于1 同步资源已经被占用了很多次
      • 双向链表,基于FIFO的同步队列
        • 通过将等待线程加入同步队列,实现线程获取同步状态失败的管理
        • 释放同步状态的时候,从同步队列中唤醒等待线程,实现同步机制。
  • 典型应用
    • 独占式:只有一个线程可以占用同步资源
      • reentrantLock
    • 共享式:多个线程占用共享资源
      • CountDownLatch
  • 上述两种方式在底层都是AQS实现的,且实现方式基本相同,区别在于获取和释放同步状态的方式不同。

AQS有哪些核心方法

  • 一共有三类方法
    • 第一类:3个访问和修改同步状态的方法
    • 第二类:5个需要重写的方法,调用上述三个方法重写
    • 第三类:9个模板方法,由外界具体调用实现不同同步组件
  • 三类方法关系:
    • 实现一个同步组件时,使用者继承AbstractQueuedSynchronizer并重写5个指定的方法(第二类)。重写同步器指定的方法时,需要使用同步器提供的3个方法来访问或修改同步状态(第一类)
    • 最后将AQS组合在自定义同步组件的实现中,并调用其9个模板方法(第三类)和 5个重写过的方法来实现,另外模板方法会调用使用者重写的方法。

总结

  • 我觉这些框架的东西不是很好理解,过分偏向于底层了,然后理解起来有比较费劲,背起来也比较难,问的也比较少,但是总是在很多地方有体现,这里还是得学习一下!
  • 这个过了,多线程编程算是过了,还有一些并发容器(ConcurrentHashMap和CopyOnWriteList)以及并发工具类没有实现,这里还是抽空再看吧!感觉问到的比较少!
  • 招工作真的不容易呀,学的我想死,每天的学习任务都好重!还是害怕找不到好工作!白读了研究生!

原文地址:https://blog.csdn.net/Blackoutdragon/article/details/140546095

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!