likes
comments
collection
share

解密多线程编程中的AQS:确保线程之间的正确协作和数据一致性

作者站长头像
站长
· 阅读数 61

前言

在并发编程中,同步是一个重要的概念。它用于控制对共享资源的访问,确保线程之间的正确协作和数据一致性。 Java并发包中的AbstractQueuedSynchronizedAQS)提供了一个强大的同步框架,可以用于构建各种同步器,如lock(锁)、semaphore(信号量)、CountDownLatch(倒计时门栓)等。本文将深入探索AQS的原理和应用、特性以及AQS的源码。

同步器是一种用于控制多线程并发访问共享资源的的机制。它确保在特定条件下只有一个线程能够执行关键代码段或访问共享资源,以保证数据的一致性和线程安全性。

AbstractQueuedSynchronized的原理和应用

AQS是一个抽象同步器,它基于FIFO(先进先出)等待队列来进行管理线程的获取和释放。它内部维护一个同步状态(Synchronization State)和一个等待队列(Wait Queue)。线程在获取同步状态时,如果同步状态已经被占用,则线程会被放入到等待队列中等待。一旦同步状态被释放,AQS会将最先插入等待队列中的的一个线程唤醒,使其能够获取同步状态并继续执行。

同步状态是AQS表示同步器状态的一个整型变量,它反映了同步器当前是否被线程占用或者是否可用。AQS通过内置的原子操作来管理同步状态的获取和释放,主要包括以下方法:

  • getState():获取同步状态的值。state == 0 代表同步状态未被占用,staet > 0 代表同步状态已被占用。
  • setState(int newState):设置同步状态的值。
  • compareAndSetState(int expecet,int update):比较并设置同步状态的值

AQS还提供了两个关键模板方法供子类实现:

  • tryAcquire(int arg):尝试获取同步状态,成功返回true,失败返回false
  • tryRelease(int arg):尝试释放同步状态,成功返回true,失败返回false

子类通过重写这两个方法来实现特定的同步语意。例如ReentrantLock就是基于AQS实现的可重入独占锁,它在tryAcquiretryRelease方法中实现了可重入和排他性访问的逻辑。

AQS的应用十分广泛,常见的应用场景包括:

  1. 锁机制:AQS可用于构建各种类型的锁,如独占锁和共享锁。ReentrantLock和ReentrantReadWriteLock,就是基于AQS实现的常用锁机制。
  2. 信号量:AQS可以用于实现信号量机制,通过控制许可数量来限制对共享资源的并发访问。Sempahore就是基于AQS实现的信号量机制、
  3. 倒计时门栓:AQS可以用于实现倒计时门栓机制,允许线程等待一组时间完成后再继续执行。CountDownLatch就是基于AQS实现的倒计时门栓。
  4. 条件变量:AQS可以用于实现条件变量机制,允许线程在某个条件满足时等待或继续执行。Condition就是基于AQS实现的条件变量。

源码详解

public abstract class AbstractQueuedSynchronizer {
    // 同步状态
    private volatile int state;

    // 等待队列的节点
    private static final class Node {
        // 线程是否共享模式
        boolean isShared;
        // 前驱节点
        Node prev;
        // 后继节点
        Node next;
        // 等待状态,表示线程等待的状态。常见的等待状态包括等待获取锁、等待被唤醒、等待条件满足等。
        int waitStatus;
        // 表示与节点关联的线程
        Thread thread;
        // 等待队列中的下一个具有相同或等待状态的节点
        Node nextWaiter;
    }
    
    //  等待队列的头部
    private transient volatile Node head;

    //  等待队列的尾部
    private transient volatile Node tail;

    // 获取同步器的状态
    protected final int getState() {
        return state;
    }

    // 设置同步器的状态
    protected final void setState(int newState) {
        state = newState;
    }

    // 使用CAS操作设置同步器的状态
    protected final boolean compareAndSetState(int expect, int update) {
        // 使用底层的原子操作来实现状态的比较和更新
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

    // 省略其他方法...
}

上述示例展示了AQS的核心代码结构,其中包含了同步状态的定义和操作方法。AQS还定义了等待队列的节点Node,用于管理等待获取同步器的线程。通过调用getState(),setState(int new Staet),compareAndSetState(int expect, int update)等方法,可以对同步状态进行读取、修改和比较操作。

扩展

公平锁和非公平锁二者区别在于获取锁的机会是否和队列中的顺序相关,当有一个线程请求锁时,下面是是二者分别获取锁的过程:

  • 公平锁:如果队列中有线程,最先进入队列的线程获得锁
  • 非公平锁:当前线程会和最先进入队列的线程竞争,竞争成功获取锁,竞争失败放入到队列的尾部。
转载自:https://juejin.cn/post/7250734439709687845
评论
请登录