深入浅出学习AQS组件

深入浅出学习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日

相关文章

  • Android实现应用内置语言切换功能

    Android实现应用内置语言切换功能攻略 在Android应用中实现应用内置语言切换功能可以让用户根据自己的偏好选择使用的语言。下面是一个详细的攻略,包含了实现该功能的步骤和两个示例说明。 步骤一:准备语言资源文件 首先,需要为每种语言准备相应的资源文件。在res目录下创建一个新的文件夹,命名为values,然后为每种语言创建一个对应的资源文件,命名规则为…

    other 2023年8月23日
    00
  • 讲解Python中if语句的嵌套用法

    Python中if语句的嵌套用法攻略 在Python中,if语句的嵌套用法允许我们在一个if语句块中嵌套另一个if语句块,以实现更复杂的条件判断。下面是详细的攻略,包含两个示例说明。 基本语法 if语句的嵌套用法的基本语法如下: if condition1: # 执行语句块1 if condition2: # 执行语句块2 else: # 执行语句块3 el…

    other 2023年7月28日
    00
  • iis 服务器应用程序不可用的解决方法

    针对“iis 服务器应用程序不可用”的问题,以下是解决方法的完整攻略。 问题背景 当我们在使用IIS(Internet Information Services)服务器,尝试打开应用程序时,出现应用程序不可用的情况。 这可能是由于多种因素引起的,包括配置不正确,端口被占用等等。下面我们一步步来解决这个问题。 解决方法 1.检查应用程序池 首先,检查应用程序池…

    other 2023年6月25日
    00
  • 在cmd命令行里进入和退出Python程序的方法

    在CMD命令行中,进入和退出Python程序需要使用Python解释器。下面是进入和退出Python程序的完整攻略。 进入Python程序 要进入Python程序,我们首先需要在CMD命令行中打开Python解释器。这可以通过输入python命令来实现。打开Python解释器后,我们就可以在命令行中开始运行Python代码了。 示例代码: C:\> p…

    other 2023年6月26日
    00
  • Java String类字符串的理解与认知

    Java String类字符串的理解与认知 Java中的String类是一个非常常用的类,用于操作字符串。它是一个不可变(immutable)的类,这意味着一旦创建了一个字符串对象,它的值就不能被更改。本攻略将会详细讲解Java String类字符串的理解与认知,包括以下内容: 创建字符串 字符串连接 字符串比较 截取子串 字符串替换 字符串转换为字符数组 …

    other 2023年6月20日
    00
  • win10怎么删除右键多余选项?win10删除桌面右键菜单多余项的方法汇总

    Win10怎么删除右键多余选项 Windows 10操作系统默认的右键菜单可能会包含一些多余选项,这些选项可能由安装的软件、驱动程序等添加的,可能会影响到用户的操作体验。本文将为你提供删除Win10系统右键菜单多余项的方法。 方法一:使用注册表删除右键菜单多余项 打开“运行”窗口(按下Win+R组合键),输入“regedit”,并按回车键,进入注册表编辑器。…

    other 2023年6月27日
    00
  • 拷贝4GB大文件到手机失败原因以及手机无法复制4GB文件解决办法介绍

    针对“拷贝4GB大文件到手机失败原因以及手机无法复制4GB文件解决办法介绍”的问题,下面提供完整的攻略: 原因分析 如果用户在尝试将4GB以上的文件拷贝到手机时失败,可能是以下原因导致: 文件系统不支持大文件拷贝:某些手机的文件系统并不支持4GB以上的文件,因此无法成功拷贝。在这种情况下,需要使用支持大文件拷贝的文件系统。 示例说明1:用户尝试将一个5GB的…

    other 2023年6月27日
    00
  • 还不懂递归?读完这篇文章保证你会懂

    下面我将为您详细讲解“还不懂递归?读完这篇文章保证你会懂”的完整攻略。 什么是递归? 递归是指函数自己调用自己,并在调用时传入一些参数。这些参数用于基础情况的处理,并且每次调用都将问题规模缩小到基础情况。如果递归没有终止条件,它将永远继续调用函数直到栈溢出。 递归的代码结构 递归的代码结构通常有以下几个部分: 基础情况。当问题被缩减到无法再继续缩减的情况时,…

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