Android Coder浅谈队列同步器(AQS)

166 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第7天,点击查看活动详情

前言

队列同步器 AbstractQueuedSynchronizer(称AQS),是用来构建锁或者其他同步组件的基础框架,使用一个 int 成员变量表示同步状态,通过内置的 FIFO 队列来完成资源获取线程的排队工作。本篇就来探讨AQS

文章总览

队列同步器-AQS

AQS使用方式

AQS 主要使用方式是继承,子类通过继承 AQS 实现它的抽象方法来管理同步状态,AQS 里由一个int 型的 state 来代表这个状态,在抽象方法的实现过程中对同步状态进行更改,同步器有提供3个方法 getState()setState(int newState)compareAndSetState(int expect,int update) 来进行操作,因为它们能够保证状态的改变是安全的。

AQS 是实现锁的关键,锁是面向使用者,定义使用者与锁交互的接口,隐藏了一些实现细节;AQS 面向的是锁的实现者,简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待、唤醒等底层操作。

实现者需要继承 AQS 并重写指定方法,然后将 AQS 组合在自定义同步组件的实现中,并调用 AQS 提供的模板方法,而这些模板方法将会调用使用者重写的方法。

模板方法设计模式

AQS 的设计师基于模板方法设计模式,模板方法设计模式是定义一个操作的算法的架子,而将一些步骤的实现延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

小案例:通过不同模型制作不同形状的蛋糕

public abstract class AbstractCake {
    protected abstract void mould(); // 制作形状
    protected abstract void butter(); // 涂奶油
    protected abstract void toast(); // 烤吐司

    public final void making() {
        this.mould();
        this.butter();
        this.toast();
    }
}

芝士(子类)继承它,重写方法:

public class Cheese extends AbstractCake {
    @Override
    protected void mould() {
        System.out.println("制作形状");
    }

    @Override
    protected void butter() {
        System.out.println("涂奶油");
    }

    @Override
    protected void toast() {
        System.out.println("烤面包");
    }
}

CLH队列锁

CLH 队列锁也是一种基于链表的可扩展、高性能、公平的自旋锁,线程仅仅在本地变量上自旋,不断轮询前驱的状态,发现前驱释放了锁就结束自旋。

当一个线程需要获取锁时:

  • 创建一个 QNode,将其中的 locked 设为 true 表示获取锁:(myPred 表示前驱节点的引用)

    qnode

  • 线程 A 对 tail 域调用 getAndSet 方法,使自己成为队列的尾部,同时获取一个指向前驱节点的引用 myPred

qnode1

线程 B 需要获得锁,于是,也需要按照相同的流程

qnode2

  • 线程就在前驱的节点的 locked 字段上自旋,直到前驱节点释放锁
  • 当一个线程需要释放锁时,会将当前节点的 locked 域设置为 false,同时回收前驱节点

qnode3

前驱节点释放锁后,线程 A 的 myPred 所指向的前驱节点的 locked 字段变为 false,线程 A 就可以获取锁。AQS 就是 CLH 队列锁的一种变体实现。

掘金(JUEJIN)  一起分享知识, Keep Learning!