[Concurrent Knowledge Points] The Implementation Principle and Application of AQS

Series Article Directory

The realization principle and application of AQS
The realization principle and application of CAS

insert image description here



foreword

In terms of Java technology, AQS refers to AbstractQueuedSynchronizer (abstract queue synchronizer). It is an important component in the Java concurrency package, which can provide a synchronization mechanism based on locks and semaphores to control access and share resources between multiple threads.


1. What is AQS?

The core idea of ​​AQS is to put the multi-threaded entry and exit operations into a FIFO (first in, first out) waiting queue, and control the concurrent access and synchronization of threads through the management of this waiting queue. Specifically, AQS uses the internal state variable to indicate the state of the lock or semaphore. When the state is 0, it means it is not occupied, and when the state is 1, it means it is occupied. In addition, AQS also provides a Condition object for suspending and waking up threads in the waiting queue.

In the application, developers can implement their own synchronization mechanism by inheriting AQS and implementing its internal acquire and release methods. The acquire method is used to acquire a lock or semaphore. When the state is 0, the thread will be added to the waiting queue, and the lock or semaphore will not be acquired until the state state becomes 1. The release method is used to release the lock or semaphore, and notify the threads in the waiting queue to continue execution.

In general, AQS is a very important component in the Java concurrent package, which provides a simple and efficient mechanism for the cooperation between multiple threads. Of course, developers need to have a deep understanding of the internal implementation of AQS in order to better use it to implement their own synchronization mechanisms.

1. Application scenarios

The application scenarios of AQS are very extensive. It can be used to implement various synchronization mechanisms such as mutexes, read-write locks, semaphores, countdown timers, and more. The most common application is the implementation of locks, such as ReentrantLock, ReentrantReadWriteLock, StampedLock, etc. These locks are implemented based on AQS, and different locks implement different synchronization strategies by implementing different tryAcquire and tryRelease methods. In addition, AQS can also be used to implement custom synchronization mechanisms, such as implementing a bounded queue, a thread pool, and so on.

2. Advantages and disadvantages

Let's analyze the advantages and disadvantages of AQS. The main advantages of AQS are flexibility, scalability, and high concurrency. It can implement various synchronization mechanisms very conveniently, and can be adaptively optimized according to different application scenarios. However, the implementation of AQS is relatively complicated, and requires a certain understanding of the implementation details of locks, and also needs to avoid problems such as deadlock and starvation. Therefore, caution is required when using AQS.

2. Case application

1. Use AQS to implement a simple mutex

The code is as follows (example):

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

In the above code, we define an internal class Sync, which inherits AbstractQueuedSynchronizer and rewrites its internal tryAcquire and tryRelease methods. In the tryAcquire method, we use the compareAndSetState method to try to acquire the lock. If the state is 0, the acquisition is successful, and the current thread is set as the lock owner; otherwise, it joins the waiting queue. In the tryRelease method, we simply set the state to 0 and the lock owner to null.

In the Mutex class, we use the Sync object as a lock, and implement the lock and unlock methods to acquire and release the lock. In this way, we can use Mutex to realize the function of mutual exclusion lock.

2. Simulated dragon boat race program

In this program, we will use AQS to implement a referee's timer, simulating a scene where multiple teams compete in a dragon boat race. Each team will create an independent thread at startup, and use Semaphore in the program to simulate the movement of the dragon boat. At the same time, the program will also use CountDownLatch to control all dragon boats to start the race at the same time, and use CyclicBarrier to simulate the celebration after all teams finish the race.
The code is as follows (example):

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

In this program, we simulated 4 teams participating in a dragon boat race. Each team creates a separate thread at startup and waits for a 1-4 second setup time before the game. When all the teams are ready, the referee sends a signal to start the race, and the dragon boat starts to move forward. The program uses Semaphore to control the number of dragon boats, and only one team can use dragon boats at a time. When a team finishes the game, the program will use CyclicBarrier to wait for other teams to finish the game and celebrate.

In short, the AQS-based Java multi-threaded program can well simulate the scene of multiple teams competing in the dragon boat race. By using various synchronization mechanisms such as Semaphore, CountDownLatch and CyclicBarrier, we can achieve complex thread cooperation and synchronization operations.


Summarize

The above is what I will talk about today. This article only briefly introduces the simple application of AQS in Java.

Finally, I wish everyone a happy Dragon Boat Festival, and a picture of making rice dumplings is attached
insert image description here

Guess you like

Origin blog.csdn.net/s445320/article/details/131339215