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

系列文章目录

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);

    public static void main(String[] args) throws InterruptedException {
    
    
        ExecutorService executorService = Executors.newFixedThreadPool(TEAM_NUM);
        for (int i = 0; i < TEAM_NUM; i++) {
    
    
            executorService.submit(new Team(i + 1));
        }
        startLatch.await(); // 等待所有队伍准备就绪
        System.out.println("比赛开始!");
        semaphore.acquire(); // 获取龙舟信号量
        System.out.println("龙舟已经准备好!");
        Thread.sleep(2000); // 等待2秒,模拟龙舟前进
        semaphore.release(); // 释放龙舟信号量
        System.out.println("比赛结束!");
        finishBarrier.await(); // 等待所有队伍完成比赛
        System.out.println("所有队伍完成比赛,开始庆祝!");
        executorService.shutdown(); // 关闭线程池
    }

    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) {
    
    
                e.printStackTrace();
            } finally {
    
    
                semaphore.release(); // 释放龙舟信号量
                try {
    
    
                    finishBarrier.await(); // 等待其他队伍完成比赛
                    System.out.println("队伍" + teamId + "正在庆祝!");
                } catch (InterruptedException | BrokenBarrierException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
    }
}

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

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


总结

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

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

猜你喜欢

转载自blog.csdn.net/s445320/article/details/131339215