【并发知识点】AQS的实现原理及应用

2 篇文章 0 订阅

系列文章目录

AQS的实现原理及应用
CAS的实现原理及应用

在这里插入图片描述



前言

在Java技术方面,AQS指的是AbstractQueuedSynchronizer(抽象队列同步器)。它是Java并发包中的一个重要组件,可以提供一种基于锁和信号量的同步机制,用于控制多线程之间的访问和共享资源。


一、AQS是什么?

AQS的核心思想是将多线程的进入和退出操作都放入一个FIFO(先进先出)的等待队列中,通过对这个等待队列的管理来控制线程的并发访问和同步。具体来说,AQS通过内部的state状态变量来表示锁或信号量的状态,当state为0时表示没有被占用,当state为1时表示被占用。此外,AQS还提供了一个Condition对象,用于在等待队列中挂起和唤醒线程。

在应用中,开发人员可以通过继承AQS并实现其内部的acquire和release方法来实现自己的同步机制。acquire方法用于获取锁或信号量,当state为0时会将线程加入等待队列中,直到state状态变为1才会获得锁或信号量。release方法则用于释放锁或信号量,并通知等待队列中的线程可以继续执行。

总的来说,AQS是Java并发包中非常重要的一个组件,它为多线程之间的协作提供了一种简单而高效的机制。当然,开发人员需要深入理解AQS的内部实现,才能更好地使用它来实现自己的同步机制。

1、应用场景

AQS的应用场景非常广泛。它可以用于实现各种同步机制,如互斥锁、读写锁、信号量、倒计时器等等。其中最常见的应用就是锁的实现,如ReentrantLock、ReentrantReadWriteLock、StampedLock等。这些锁都是基于AQS实现的,不同的锁通过实现不同的tryAcquire和tryRelease方法来实现不同的同步策略。此外,AQS还可以用于实现自定义同步机制,如实现一个有界队列、一个线程池等等。

2、优缺点

我们来分析一下AQS的优缺点。AQS的主要优点是灵活性、可扩展性和高并发性。它可以非常方便地实现各种同步机制,并且能够自适应地根据不同的应用场景进行优化。但是,AQS的实现比较复杂,需要对锁的实现细节有一定的了解,同时也需要避免出现死锁和饥饿等问题。因此,在使用AQS时需要谨慎操作。

二、案例应用

1.使用AQS来实现一个简单的互斥锁

代码如下(示例):

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class Mutex {
    private static class Sync extends AbstractQueuedSynchronizer {
        // 当state为0时,表示锁没有被占用;当为1时,表示锁已被占用
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        // 尝试获取锁,如果state为0,则获取成功;否则加入等待队列
        public boolean tryAcquire(int acquires) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        // 释放锁
        protected boolean tryRelease(int releases) {
            if (getState() == 0) throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
    }

    // 创建一个Sync对象作为锁
    private final Sync sync = new Sync();

    // 获取锁
    public void lock() {
        sync.acquire(1);
    }

    // 释放锁
    public void unlock() {
        sync.release(1);
    }
}

在上面的代码中,我们定义了一个内部类Sync,它继承了AbstractQueuedSynchronizer并重写了其内部的tryAcquire和tryRelease方法。在tryAcquire方法中,我们使用compareAndSetState方法来尝试获取锁,如果state为0,则获取成功,并将当前线程设置为锁拥有者;否则加入等待队列。在tryRelease方法中,我们简单地将state设置为0,并将锁拥有者设置为null。

在Mutex类中,我们将Sync对象作为锁,并实现了lock和unlock方法来获取和释放锁。这样,我们就可以使用Mutex来实现互斥锁的功能了。

2.模拟赛龙舟程序

在这个程序中,我们将使用AQS来实现一个裁判的计时器,模拟一个赛龙舟比赛中多支队伍竞争的场景。每个队伍都会在启动时创建一个独立的线程,并在程序中使用Semaphore来模拟龙舟的运动。同时,程序中还会使用CountDownLatch来控制所有龙舟同时开始比赛,并使用CyclicBarrier来模拟所有队伍完成比赛后的庆祝活动。
代码如下(示例):

import java.util.concurrent.*;

public class DragonBoatRace {
    private static final int TEAM_NUM = 4; // 参赛队伍数
    private static final int BOAT_NUM = 1; // 龙舟数量
    private static final Semaphore semaphore = new Semaphore(BOAT_NUM);
    private static final CountDownLatch startLatch = new CountDownLatch(TEAM_NUM);
    private static final CyclicBarrier finishBarrier = new CyclicBarrier(TEAM_NUM, new Runnable() {
        @Override
        public void run() {
            // 当所有队伍完成比赛时,执行的动作
            System.out.println("所有队伍完成比赛,开始庆祝!");
        }
    });

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(TEAM_NUM);
        for (int i = 0; i < TEAM_NUM; i++) {
            executorService.submit(new Team(i + 1));
        }
        try {
            startLatch.await(); // 等待所有队伍准备就绪
            System.out.println("比赛开始!");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.out.println("比赛开始被中断");
        } finally {
            executorService.shutdown(); // 关闭线程池
        }

        try {
            // 等待线程池中的所有任务完成
            if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
                // 超时后强制关闭
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            // 如果等待被中断,立即关闭
            executorService.shutdownNow();
        }

        System.out.println("比赛结束!");
    }

    static class Team implements Runnable {
        private final int teamId;

        public Team(int teamId) {
            this.teamId = teamId;
        }

        @Override
        public void run() {
            try {
                Thread.sleep(1000 * teamId); // 模拟队伍准备时间
                System.out.println("队伍" + teamId + "已准备就绪!");
                startLatch.countDown(); // 准备就绪,计数器减一
                semaphore.acquire(); // 获取龙舟信号量
                System.out.println("队伍" + teamId + "已上船,准备出发!");
                Thread.sleep(2000); // 等待2秒,模拟龙舟前进
                System.out.println("队伍" + teamId + "已完成比赛!");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                System.out.println("队伍" + teamId + "被中断");
            } finally {
                semaphore.release(); // 释放龙舟信号量
                try {
                    finishBarrier.await(); // 等待其他队伍完成比赛
                } catch (InterruptedException | BrokenBarrierException e) {
                    Thread.currentThread().interrupt();
                    System.out.println("队伍" + teamId + "在等待其他队伍时被中断或者barrier被破坏");
                }
            }
        }
    }
}


在这个程序中,我们模拟了4个队伍参加赛龙舟比赛。每个队伍都在启动时创建一个独立的线程,并在比赛前等待1~4秒的准备时间。当所有队伍都准备就绪后,裁判发出比赛开始信号,龙舟开始前进。程序中使用Semaphore来控制龙舟数量,每次只有一个队伍可以使用龙舟。当某个队伍完成比赛后,程序会使用CyclicBarrier来等待其他队伍完成比赛,并且进行庆祝活动。

总之,基于AQS的Java多线程程序可以很好地模拟赛龙舟比赛中的多支队伍竞争的场景。通过使用Semaphore、CountDownLatch和CyclicBarrier等多种同步机制,我们可以实现复杂的线程协作和同步操作。


总结

以上就是今天要讲的内容,本文仅仅简单介绍了AQS在Java中的简单应用。

最后祝大家端午快乐,附包粽子图一张
在这里插入图片描述

  • 13
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
AQS(AbstractQueuedSynchronizer)是Java中实现同步器的框架,它提供了一种基于FIFO队列的阻塞和唤醒机制。AQS的阻塞队列原理是通过CLH(Craig, Landin, and Hagersten)队列来实现的。 CLH队列是一种虚拟的双向链表,它仅存在节点之间的关联关系,而不存在队列的实例。每个请求共享资源的线程都会被封装成一个CLH队列的节点(Node)。当线程请求共享资源时,它会被添加到CLH队列的尾部,并进入阻塞状态。 当共享资源被占用时,其他线程请求该资源的线程会被放入CLH队列的末尾,即排队等待。这种排队等待的方式可以保证请求资源的线程按照FIFO的顺序获得资源,避免了饥饿现象。当资源释放后,AQS会自动唤醒队列中的下一个线程,使其获得资源并继续执行。 需要注意的是,AQS的同步队列(Sync queue)是一个双向链表,包括头节点(head)和尾节点(tail),用于后续的调度。而条件队列(Condition queue)是一个单向链表,只有在使用Condition时才会存在,并且可能会有多个条件队列。 总结一下,AQS实现阻塞队列的原理是通过CLH队列来实现的,当共享资源被占用时,请求资源的线程会被添加到CLH队列中排队等待。当资源释放后,AQS会自动唤醒队列中的下一个线程,使其获得资源并继续执行。同步队列用于后续的调度,而条件队列只在使用Condition时才会存在。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青花锁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值