Java并发之AQS详解

带着问题阅读

1、什么是AQS,它有什么作用,核心思想是什么

2、AQS中的独占锁和共享锁原理是什么,AQS提供的锁机制是公平锁还是非公平锁

3、AQS在Java中有哪些实现,如何基于AQS实现自己的锁控制

4、AQS除了提供锁框架以外还提供了什么能力

AQS介绍

AbstractQueuedSynchronizer(AQS)提供了一套可用于实现锁同步机制的框架,不夸张地说,AQSJUC同步框架的基石。AQS通过一个FIFO队列维护线程同步状态,实现类只需要继承该类,并重写指定方法即可实现一套线程同步机制。

AQS根据资源互斥级别提供了独占和共享两种资源访问模式;同时其定义Condition结构提供了wait/signal等待唤醒机制。在JUC中,诸如ReentrantLockCountDownLatch等都基于AQS实现。

AQS框架

AQS原理

AQS的原理并不复杂,AQS维护了一个volatile int state变量和一个CLH(三个人名缩写)双向队列,队列中的节点持有线程引用,每个节点均可通过getState()setState()compareAndSetState()state进行修改和访问。·

当线程获取锁时,即试图对state变量做修改,如修改成功则获取锁;如修改失败则包装为节点挂载到队列中,等待持有锁的线程释放锁并唤醒队列中的节点。

AQS模版方法

AQS内部封装了队列维护逻辑,采用模版方法的模式提供实现类以下方法:

tryAcquire(int);        // 尝试获取独占锁,可获取返回true,否则false
tryRelease(int);        // 尝试释放独占锁,可释放返回true,否则false
tryAcquireShared(int);  // 尝试以共享方式获取锁,失败返回负数,只能获取一次返回0,否则返回个数
tryReleaseShared(int);  // 尝试释放共享锁,可获取返回true,否则false
isHeldExclusively();    // 判断线程是否独占资源
复制代码

如实现类只需实现独占锁/共享锁功能,可只实现tryAcquire/tryReleasetryAcquireShared/tryReleaseShared。虽然实现tryAcquire/tryRelease可自行设定逻辑,但建议使用state方法对state变量进行操作以实现同步类。

如下是一个简单的同步锁实现示例:

public class Mutex extends AbstractQueuedSynchronizer {
    
    @Override
    public boolean tryAcquire(int arg) {
        return compareAndSetState(0, 1);
    }
    
    @Override
    public boolean tryRelease(int arg) {
        return compareAndSetState(1, 0);
    }
    
    public static void main(String[] args) {
        final Mutex mutex = new Mutex();
        
        new Thread(() -> {
            System.out.println("thread1 acquire mutex");
            mutex.acquire(1);
            // 获取资源后sleep保持
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch(InterruptedException ignore) {
                
            }
            mutex.release(1);
            System.out.println("thread1 release mutex");
        }).start();
        
        new Thread(() -> {
            // 保证线程2在线程1启动后执行
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch(InterruptedException ignore) {
                
            }
            // 等待线程1 sleep结束释放资源
            mutex.acquire(1);
            System.out.println("thread2 acquire mutex");
            mutex.release(1);
        }).start()
    }
}
复制代码

示例代码简单通过AQS实现一个互斥操作,线程1获取mutex后,线程2的acquire陷入阻塞,直到线程1释放。其中tryAcquire/acquire/tryRelease/releasearg参数可按实现逻辑自定义传入值,无具体要求。

@param arg the acquire argument. This value is conveyed to {@link #tryAcquire} but is otherwise uninterpreted and can represent anyting you like.

AQS核心结构

Node

前文提到,在AQS中如果线程获取资源失败,会包装成一个节点挂载到CLH队列上,AQS中定义了Node类用于包装线程。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享