【并发专题】AQS 连环炮

本问题对应的项目链接:高频题回答模版训练

说明:一般来说,即时有问AQS,也不会问很难,我们只需要从整体的角度,知道他是如何保证线程安排的就可以了,AQS的底层也是基于CAS的。

1、了解 AQS 吗?AQS 的全名叫什么?(PS:这个问题,面试官还真问过)

2、大致讲解一下 AQS 是怎么实现线程安全的?

3、基于AQS的实现的同步工具,你了解哪些?(PS:简单介绍两三个就可以了)

共以下 3 个回答

  • 終达·斯维达尼亚 永久会员 2024年2月28日 下午9:08

    1、了解 AQS 吗?AQS 的全名叫什么?

    AQS是一个用来构建锁或其他同步组件的框架,内部用一个volitale修饰的state变量来表示同步的状态,用一个先入先出的队列来完成线程的排队工作。AQS全称是AbstractQueuedSynchronizer,直译是抽象队列同步器,是一个抽象类,由子类继承该类的抽象方法来管理同步状态。

    2、大致讲解一下 AQS 是怎么实现线程安全的?

    首先AQS使用一个volitale修饰的state变量来表示同步状态,并通过一个先入先出队列来实现线程排队。对于独占式同步状态的获取和释放,队列中每一个节点就代表一个等待的线程,一个线程请求获取锁,成功则将该线程作为头结点,失败则进入尾节点排队。当当前线程释放锁的时候,会唤醒头结点的后继结点,后继结点获取锁并将自己设置为头结点,以此往复。在此过程中锁的获取是通过CAS机制保证的,因此队列头结点和尾节点设置都是线程安全的。针对共享锁,AQS会设置一个计数器规定最多有多少线程能共享,当有线程进入的时候通过CAS机制将计数器减1。

    3、基于AQS的实现的同步工具,你了解哪些?

    • ReenTrantLock:可重入锁,默认为非公平锁,通过内部一个计数器计数加锁次数。
    • CountDownLatch:计数同步类,他可以让当前线程处于等待状态,在代码中可以设置在哪执行自减,等计数为0时才能继续往下执行,计数器依靠CAS机制自减。
    • CyclicBarrier:当指定的线程数都执行到某个位置的时候,他才会继续往下执行。可以让几个线程相互等待,等人到齐了在一起往下执行。依靠ReenTrantLock实现线程安全的。
    • CountDownLatch和CyclicBarrier的异同
      • CyclicBarrier是多线程互相等待,CountDownLatch是一个线程等待其他线程执行完毕
      • 同步锁上CountDownLatch是基于CAS机制实现的,CyclicBarrier是基于ReenTrantLock实现的
    • semaphore:可以控制某个资源最多可以被几个线程所持有。
    0 赞同 0 条回复
  • Gary 永久会员 2023年7月9日 下午8:50

    1、AQS全名叫做AbstractQueuedSynchronizer,抽象队列同步器,它是一个抽象类,提供了一种基于先进先出的等待队列框架,用来实现锁和同步工具,内部有一个int类型的变量state,用来记录锁的状态,用来判断锁是否被占用,同时AQS定义了一些获取和释放锁的方法,AQS的具体功能,都是由它的子类来实现

    2、AQS底层使用CAS来保证对锁和队列节点的操作是线程安全的。
    对于互斥锁,AQS把当前等待锁的线程和等待状态封装成一个node节点,这个节点会加入到等到队列里面,当线程释放锁的时候,会唤醒队列里面的第一个节点,让它获取锁。
    对于共享锁,AQS使用一个计数器来跟踪当前的锁还能被多少个线程获取,当一个线程获取到锁,计数器减一;当一个线程释放锁,计数器加一,当计数器结果为0的时候,就不能再获取锁了,此时新来的线程要进入等待队列,如果有线程释放锁,就唤醒队列里面的线程,针对读写锁,当写锁被释放的时候,会唤醒所有的读线程,但是只唤醒第一个写线程

    3、AQS常见的子类有ReentrantLock、CountDownLatch、CycliBarrier、Semaphore。
    ReentrantLock可以用来加锁和解锁,支持公平锁和非公平锁,支持响应中断;
    CountDownLatch用来让当前线程等待指定的线程数,等这些线程执行完之后,才能往下继续执行;
    CycliBarrier是当指定的线程数到达某个位置的时候,才继续往下执行;
    Semaphore是指定一个共享变量,同一时间最多只能被多少个线程占有。

    0 赞同 0 条回复
  • 秊 永久会员 2023年7月4日 下午5:02

    1、了解 AQS 吗?AQS 的全名叫什么?(PS:这个问题,面试官还真问过)

    AQS 是用来构建锁或者其它同步组件的基础框架,内部用一个 int 变量来表示同步状态,并用 FIFO 队列来完成资源获取线程的排队工作。

    它全称为 AbstractQueuedSynchronizer 抽象队列同步器,是一个抽象类,通常由子类继承 AQS 并实现它的抽象方法来管理同步状态。

    2、大致讲解一下 AQS 是怎么实现线程安全的?

    可以从三个方面来说:底层数据结构,独占式同步状态的获取与释放和共享式同步状态的获取与释放。

    首先 AQS 底层使用 volatile int 变量 state 来表示同步状态,通过内部的 FIFO 双向队列来实现多线程的排队工作。

    对于独占式同步状态的获取和释放来说,队列的一个节点代表一个线程及其等待状态,若某个线程请求获取同步状态,成功则将其作为队列的头结点,失败则将其添加到队列的尾部并尝试自旋。当头结点的线程释放同步状态时,会同时唤醒头结点的后继节点,后继节点在获取同步状态成功后会将自己设置为头结点,以此类推。需要注意的是,这整个过程需要用 CAS 操作保证同步状态的获取,队列头尾节点的设置都是线程安全的。

    共享式获取与独占式获取的区别在于,共享式访问资源时,其它共享式的访问都被允许,而独占式访问将被阻塞。正是由于同时可能有多个线程拥有同步状态,共享式释放同步状态也需要用 CAS 确保线程安全。

    3、基于AQS的实现的同步工具,你了解哪些?(PS:简单介绍两三个就可以了)

    比如重入锁 ReentrantLock,读写锁 ReetrantReadWriteLock 和等待多线程完成的 CountDownLatch,底层都是基于 AQS 实现的。

    ReentrantLock 可以支持一个线程对某个资源的重复加锁,底层是在同一个线程加锁时递增,释放锁时递减同步状态的值。

    ReetrantReadWriteLock 内部维护了一对锁,一个读锁和一个写锁。多个线程可以同时获得读锁,并发地进行读取操作。但是,只有一个线程能够获得写锁,同时其他线程获得读锁或写锁将被阻塞,确保了在写操作期间没有其他线程能够读取或写入数据。

    CountDownLatch 用于一个或多个线程等待其他线程完成操作。内部维护了一个计数值,任一线程在调用 await( ) 时,如果当前计数值不为 0 则会被阻塞,直到计数值减至 0 为止。而其他线程可以调用countDown( ) 来减小计数值。

    4、demo

    ReentrantReadWriteLock 示例

    public class ReentrantReadWriteLockExample {
        private final Map<String, String> files;  // 虚拟文件系统中的文件
        private final ReentrantReadWriteLock lock;  // 读写锁
    
        public ReentrantReadWriteLockExample() {
            files = new HashMap<>();
            lock = new ReentrantReadWriteLock();
        }
    
        public String readFile(String fileName) {
            // 获取读锁, 允许多个线程同时读
            lock.readLock().lock();
    
            try {
                System.out.println("Reading file: " + fileName);
                // 模拟读取文件的耗时操作
                Thread.sleep(1000);
                return files.getOrDefault(fileName, null);
            } catch (InterruptedException e) {
                e.printStackTrace();
                return null;
            } finally {
                // 释放读锁
                lock.readLock().unlock();
            }
        }
    
        public void writeFile(String fileName, String content) {
            // 获取写锁, 允许一个线程独占写
            lock.writeLock().lock();
            try {
                System.out.println("Writing file: " + fileName);
                // 模拟写入文件的耗时操作
                Thread.sleep(1000);
                files.put(fileName, content);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 释放写锁
                lock.writeLock().unlock();
            }
        }
    
        public static void main(String[] args) {
            ReentrantReadWriteLockExample fileSystem = new ReentrantReadWriteLockExample();
    
            // 开启十个写线程
            for (int i = 0; i < 10; ++i) {
                int finalI = i;
                new Thread(() -> {
                    fileSystem.writeFile("file" + finalI, "Hello, World!");
                }).start();
            }
    
            // 开启四个读线程
            for (int i = 0; i < 4; ++i) {
                int finalI = i;
                new Thread(() -> {
                    String content = fileSystem.readFile("file" + finalI);
                    System.out.println("Read content: " + content);
                }).start();
            }
    
        }
    }
    

    运行后,控制台可以看到十个写线程是顺序打印输出(独占),四个读线程是一块打印输出(共享)。

    CountDownLatch 示例

    public class CountDownLatchExample {
        public static void main(String[] args) throws InterruptedException {
            // 创建一个 CountDownLatch 实例,参数为需要等待的线程数量
            CountDownLatch latch = new CountDownLatch(3);
    
            // 创建三个工作线程
            List<Thread> workers = Stream
                    .generate(() ->
                            new WorkerThread("Worker", latch)
                    )
                    .limit(3)
                    .collect(toList());
    
            // 启动所有线程
            workers.forEach(Thread::start);
    
            // 等待所有工作线程完成操作
            latch.await();
    
            // 所有工作线程完成后执行的代码
            System.out.println("工作线程执行完成");
        }
    
        static class WorkerThread extends Thread {
            private final CountDownLatch latch;
    
            public WorkerThread(String name, CountDownLatch latch) {
                super(name);
                this.latch = latch;
            }
    
            @Override
            public void run() {
                try {
                    // 模拟工作线程执行任务
                    System.out.println(getName() + " is performing task...");
                    // 模拟任务执行时间
                    Thread.sleep(2000);
    
                    System.out.println(getName() + " has finished its task.");
    
                    // 任务完成后调用 countDown() 方法,减少计数器
                    latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    0 赞同 0 条回复

# 回答此问题

后才能回答