深入浅出学习AQS组件

yizhihongxing

深入浅出学习AQS组件攻略

什么是AQS

AQS (AbstractQueuedSynchronizer) 是 Java 并发包提供的一个用于构建锁和同步器的基础框架,是Java并发编程中重要的底层实现。

AQS的设计思想是对java.util.concurrent包所有同步器的公共行为进行抽象和封装,以便于在实现具体同步器(如ReentrantLock、Semaphore等)时使用,该组件的实现使用了一个FIFO队列和一个int变量(state)来实现对同步状态的管理,具有灵活性和扩展性,可以方便地支持类似独占锁和共享锁这样的同步语义。

AQS实现原理

AQS的实现原理可以在深度掌握Java并发编程知识的基础上来进行深入研究和理解。

AQS内部使用一个FIFO队列来记录等待其他线程释放锁的线程,这个队列的元素为Node。每个Node包含了线程本身的信息以及该线程在等待队列中的前驱和后继节点信息,线程这种方式被包装起来以便于同步队列管理。

AQS要求实现的同步器需要维护一个int变量state,state表示同步器的状态,具体含义由同步器自己来定义。

AQS通过CAS操作(Compare And Swap,即无阻塞的乐观锁机制)来保证对共享资源的访问的正确性,当一个线程试图获取锁时,AQS会首先尝试原子地把state的值的某个位(或几个位)由0改为1,如果成功了则说明该线程获取了锁,否则,AQS会把这个线程“安排”到等待队列中为获取到锁而等待。当锁被持有者释放时,AQS会从等待队列中唤醒一个线程,被唤醒的线程会再次尝试获取锁。

AQS的应用

AQS可以用于实现独占锁和共享锁两种同步方式。

独占锁

独占锁只能由一个线程来占有,实现可重入的、互斥访问共享资源的目的。其常见的实现是ReentrantLock类。

具体实现代码如下:

public class MyReentrantLock extends AbstractQueuedSynchronizer {

    /**
     * Returns true if lock is held by current thread else false.
     * @return boolean
     */
    @Override
    protected boolean isHeldExclusively() {
        return getState() == 1;
    }

    /**
     * Try to acquire lock.
     * From AbstractQueuedSynchronizer#tryAcquire
     *
     * @param args args
     * @return boolean
     */
    @Override
    protected boolean tryAcquire(int args) {
        if(compareAndSetState(0, 1)) {
            // Exclusive ownership has been established
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    /**
     * Try to release lock.
     * From AbstractQueuedSynchronizer#tryRelease
     *
     * @param args args
     * @return boolean
     */
    @Override
    protected boolean tryRelease(int args) {
        if (getState() == 0) {
            throw new IllegalMonitorStateException();
        }
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
    }

}

从代码中可以看到,MyReentrantLock继承了AbstractQueuedSynchronizer,并实现了父类的三个方法用于协同控制锁的获取和释放。

共享锁

共享锁允许多个线程同时访问共享资源,不能独占资源。Semaphore和CountDownLatch就是基于共享锁同步器实现的。

Semaphore

Semaphore是用来控制同时访问特定资源的线程数量,常用于限制线程并发访问的情况。一个Semaphore实例有一个permits变量和一个等待队列,permits表示最多有多少个线程同时访问资源,当获取许可时,permits减1;当释放许可时,permits加1。

从下面的示例代码可以看出Semaphore用法。

public class SemaphoreTest {

    static class MyTask implements Runnable {

        Semaphore semaphore;

        MyTask(Semaphore semaphore) {
            this.semaphore = semaphore;
        }

        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println(Thread.currentThread().getName() + " 获取许可,开始执行");
                Thread.sleep((long) (Math.random() * 1000));
                semaphore.release();
                System.out.println(Thread.currentThread().getName() + " 释放许可,继续执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

    public static void main(String[] args) {
        int permits = 5;
        Semaphore semaphore = new Semaphore(permits);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            MyTask task = new MyTask(semaphore);
            executorService.execute(task);
        }
        executorService.shutdown();
    }
}

运行结果如下:

pool-1-thread-1 获取许可,开始执行
pool-1-thread-5 获取许可,开始执行
pool-1-thread-2 获取许可,开始执行
pool-1-thread-3 获取许可,开始执行
pool-1-thread-8 获取许可,开始执行
pool-1-thread-6 获取许可,开始执行
pool-1-thread-9 获取许可,开始执行
pool-1-thread-4 获取许可,开始执行
pool-1-thread-7 获取许可,开始执行
pool-1-thread-5 释放许可,继续执行
pool-1-thread-3 释放许可,继续执行
pool-1-thread-1 释放许可,继续执行
pool-1-thread-6 释放许可,继续执行
pool-1-thread-4 释放许可,继续执行
pool-1-thread-2 释放许可,继续执行
pool-1-thread-8 释放许可,继续执行
pool-1-thread-7 释放许可,继续执行
pool-1-thread-9 释放许可,继续执行

从结果中可以看出,最多只有5个线程同时被允许执行。当许可被占满时,后续线程会进入等待队列,等待正在执行的线程释放许可。

CountDownLatch

CountDownLatch是一种多线程同步工具,允许一个或多个线程等待其他线程完成操作。CountDownLatch初始化时需要一个计数器,表示需要等待的线程数量;每个线程完成操作后,计数器减1;当计数器为0时,所有等待线程被唤醒。

public class CountDownLatchTest {

    static class Worker implements Runnable {
        private final CountDownLatch latch;

        Worker(CountDownLatch latch) {
            this.latch = latch;
        }

        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + " 正在执行");
                Thread.sleep((long) (Math.random() * 10000));
                System.out.println(Thread.currentThread().getName() + " 执行完毕");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                latch.countDown();
            }
        }
    }

    public static void main(String[] args) {
        CountDownLatch latch = new CountDownLatch(5);
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            executor.execute(new Worker(latch));
        }
        executor.shutdown();
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("所有线程执行完毕");
        }
    }
}

运行结果如下:

pool-1-thread-1 正在执行
pool-1-thread-4 正在执行
pool-1-thread-5 正在执行
pool-1-thread-2 正在执行
pool-1-thread-3 正在执行
pool-1-thread-2 执行完毕
pool-1-thread-1 执行完毕
pool-1-thread-5 执行完毕
pool-1-thread-4 执行完毕
pool-1-thread-3 执行完毕
所有线程执行完毕

可以看到,所有线程执行完毕后,主线程才继续向下执行。如果没有CountDownLatch等待线程的机制,主线程无法保证输出语句在所有线程运行完毕后再被执行。

总结来说,AQS提供了一种底层、灵活的实现多种同步方式的框架,既方便了同步器的实现,也为高层级的同步工具(如ReentrantLock、Semaphore、CountDownLatch等)提供了基础。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:深入浅出学习AQS组件 - Python技术站

(0)
上一篇 2023年6月27日
下一篇 2023年6月27日

相关文章

  • layui自定义工具栏的方法

    下面是layui自定义工具栏的完整攻略: 1. 确定工具栏配置 首先需要确定自定义工具栏的配置项,例如:需要添加什么按钮、需要设置按钮绑定的事件等。 在layui中,工具栏的配置项可以通过form.render()方法进行设置,其中form是layui的一个内置模块,用于处理表单数据和各种表单元素的渲染等操作。 具体的实现方法如下: layui.use([‘…

    other 2023年6月25日
    00
  • openstack 重启的服务命令整理总结

    这里是关于 “OpenStack 重启的服务命令整理总结” 的详细攻略。 背景 在 OpenStack 的运维过程中,经常需要对服务进行重启,比如某些服务出现故障、更新配置文件等。本文将对 OpenStack 中常见的服务进行整理和总结,列出对应的服务重启命令。 Keystone Keystone 是 OpenStack 的身份认证服务,管理 OpenSta…

    other 2023年6月27日
    00
  • 手机扩展内存和自带内存一样吗 扩展内存和自带内存哪个更好

    手机扩展内存和自带内存一样吗? 手机扩展内存和自带内存并不完全一样,它们有一些区别。下面将详细讲解这两者之间的差异。 自带内存 自带内存是指手机出厂时已经内置在手机中的存储空间。它通常是固定的,无法更改或扩展。自带内存的容量决定了手机可以存储的应用程序、媒体文件和其他数据的数量。较高容量的自带内存可以提供更大的存储空间,但也会增加手机的成本。 扩展内存 扩展…

    other 2023年8月1日
    00
  • 详解使用Spring Cloud Consul实现服务的注册和发现

    详解使用Spring Cloud Consul实现服务的注册和发现的攻略如下: 1. 环境配置 首先,我们需要在项目的pom.xml文件中添加Spring Cloud Consul的依赖: <dependency> <groupId>org.springframework.cloud</groupId> <artif…

    other 2023年6月27日
    00
  • IOS自定义UIView

    下面是详细讲解“IOS自定义UIView”的完整攻略。 1. 概述 在iOS开发中,UIView是我们常用的控件,可以用来展示内容,处理用户的交互操作。但是有时候,系统提供的UIView并不能满足我们的需求,我们需要自定义UIView来实现我们想要的功能。 在自定义UIView的过程中,我们可以通过继承UIView类来实现对UIView的扩展。在UIView…

    other 2023年6月25日
    00
  • iOS 14.5/iPadOS 14.5(18E199) RC准正式版更新(附更新内容)

    iOS 14.5/iPadOS 14.5(18E199) RC准正式版更新攻略 iOS 14.5/iPadOS 14.5(18E199) RC准正式版是苹果公司最新发布的操作系统更新版本。本攻略将详细介绍该版本的更新内容,并提供两个示例说明。 更新内容 App Tracking Transparency (ATT) 该更新引入了App Tracking Tr…

    other 2023年8月3日
    00
  • java设计模式之静态工厂模式详解

    Java设计模式之静态工厂模式详解 静态工厂模式是一种创建型设计模式,它提供了一种创建对象的方法,而无需暴露对象的创建逻辑。本文将提供一个完整攻略,介绍静态工厂模式的使用方法和注意事项,并提供两个示例说明。 静态工厂模式的使用方法 静态工厂模式是通过一个静态方法来创建对象的。可以按照以下步骤实现: 创建一个静态工厂类,该类包含一个静态方法,用于创建对象。 在…

    other 2023年5月8日
    00
  • 简单了解java中int和Integer的区别

    下面就为大家详细讲解一下“简单了解Java中int和Integer的区别”。 什么是int和Integer类型? 在Java中,int是一种基本数据类型,它表示整型数值。Java中还有一种数据类型Integer,它是int的封装类,也是一种对象类型。 int和Integer类型的区别 类型 int是基本数据类型,只包含数值,而Integer是对象类型,它包含…

    other 2023年6月27日
    00
合作推广
合作推广
分享本页
返回顶部