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

Series Article Directory

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

insert image description here



foreword

This chapter introduces the concept and implementation principle of CAS, and applies it through java code, finally simulating the dragon boat race.

1. The concept of CAS

The full name of CAS is: CompareAndSwap, literally translated as comparison and exchange.
CAS is actually an instruction supported by common processors. This instruction compares and sets a new value by judging whether the current memory value V, the old expected value A, and the value B to be updated are equal, so as to realize the atomicity of variables.
Synchronized thread blocking is called pessimistic locking, and CAS does not make thread blocking called optimistic locking. Pessimistic locks and other threads that have not acquired the lock will not execute the code, while optimistic locking allows multiple threads to access the code at the same time, but it will judge whether there is an update and decide whether to re-execute.

2. The realization principle of CAS

The principle of CAS: through three parameters, the variable value V of the current memory, the old expected value A, and the value B to be updated. By judging whether V and A are equal, check whether the current variable value has been changed by other threads. If they are equal, the variable has not been changed by other threads, and then assign the value of B to V; if they are not equal, perform a spin operation.
Example: Assume that the initial value of i is 0, and now two threads execute the i++ operation respectively, and see how CAS will execute:

1 thread: A=0, B=1
2 threads: A=0, B=1

At this time, the old expected value A and the updated B value of the two threads are the same.
Assume that thread 1 executes first, and thread 1 gets the value V of i from the memory.
2 When the thread executes, the V value of the memory obtained at this time is 1, and the old expected value 0 does not want to wait, so the assignment operation of updating B is not performed, and the old expected value A=V is determined by spinning, and the CAS instruction is determined again.
insert image description here

The atomic operation classes provided by JDK are based on CAS to achieve atomicity, such as: AtomicInteger, AtomicIntegerArray, AtomicDouble, AtomicBoolean, etc.

3. Single JVM internal lock CAS implementation

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/***
 * @title AtomicExample
 * @desctption CAS
 * @author Kelvin
 * @create 2023/6/14 17:08
 **/
public class AtomicExample {
    
    
    private static Map<String, Boolean> map = new HashMap<>();

    public static void main(String[] args) throws InterruptedException {
    
    
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //未加锁
        map.put("lock", true);
        for (int i = 0; i < 20; i++) {
    
    
            int finalI = i;
            executorService.submit(
                    () -> {
    
    
                        boolean isRotate = true;
                        while (isRotate) {
    
    
                            boolean vLock = map.get("lock");
                            if( vLock ) {
    
    
                                boolean aLock = map.get("lock");
                                if( vLock == aLock ) {
    
    
                                    //执行业务逻辑
                                    //加锁
                                    map.put("lock", false);
                                    System.out.println( "执行业务逻辑, i: " + finalI);
                                    try {
    
    
                                        Thread.sleep(2000);
                                    } catch (InterruptedException e) {
    
    
                                        throw new RuntimeException(e);
                                    }
                                    isRotate = false;
                                    //释放锁
                                    map.put("lock", true);
                                } else {
    
    
                                    System.out.println("自旋,重新获取锁!");
                                    continue;
                                }
                            }
                        }
                    }
            );
        }

        Thread.sleep(20 * 1000);
        System.out.println("end");
        executorService.shutdown();
        
    }
}

3.1. Effect

Execute business logic, i: 1
execute business logic, i: 5
spin, re-acquire the lock!
Spin, re-acquire the lock!
Spin, re-acquire the lock!
Spin, re-acquire the lock!
Spin, re-acquire the lock!
Spin, re-acquire the lock!
Execute business logic, i: 4
spins, re-acquire the lock!
Spin, re-acquire the lock!
Execute business logic, i: 10
spins, re-acquire the lock!
Spin, re-acquire the lock!
Execute business logic, i: 9
execute business logic, i: 2
spin, re-acquire the lock!
Execute business logic, i: 3
spins, re-acquire the lock!
Execute business logic, i: 0
execute business logic, i: 12
execute business logic, i: 11
execute business logic, i: 8 execute business logic
, i: 6
execute business logic, i: 7
execute business logic, i: 14
spin, re-acquire the lock!
Execute business logic, i: 15
execute business logic, i: 16
execute business logic, i: 18
spin, re-acquire the lock!
Spin, re-acquire the lock!
Execute business logic, i: 17
Execute business logic, i: 19
Execute business logic, i: 13
end

4. Simulated dragon boat race

In this program, we will use Java's AtomicInteger 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 LockFreeQueue 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.

import java.util.Random;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class DragonBoatRace {
    
    
    private static final int TEAM_NUM = 4; // 参赛队伍数
    private static final int BOAT_NUM = 1; // 龙舟数量
    private static final Random random = new Random();
    private static final AtomicInteger time = new AtomicInteger(0);
    private static final CountDownLatch startLatch = new CountDownLatch(TEAM_NUM);
    private static final CyclicBarrier finishBarrier = new CyclicBarrier(TEAM_NUM + 1);
    private static final LockFreeQueue<Integer> queue = new LockFreeQueue<>();

    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("比赛开始!");
        for (int i = 0; i < BOAT_NUM; i++) {
    
    
            new Thread(new Boat(i + 1)).start();
        }
        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(); // 准备就绪,计数器减一
                queue.add(1); // 龙舟前进一步
                while (time.get() < 2000) {
    
     // 等待龙舟到达终点
                    Thread.yield();
                }
                System.out.println("队伍" + teamId + "已完成比赛,用时" + time.get() + "ms!");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                try {
    
    
                    finishBarrier.await(); // 等待其他队伍完成比赛
                    System.out.println("队伍" + teamId + "正在庆祝!");
                } catch (InterruptedException | BrokenBarrierException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
    }

    static class Boat implements Runnable {
    
    
        private final int boatId;

        public Boat(int boatId) {
    
    
            this.boatId = boatId;
        }

        @Override
        public void run() {
    
    
            while (!queue.isEmpty()) {
    
     // 龙舟前进,直到到达终点
                if (queue.poll() != null) {
    
    
                    time.addAndGet(random.nextInt(10) + 1); // 龙舟前进一步,用时1~10ms
                }
            }
            try {
    
    
                finishBarrier.await(); // 等待所有队伍完成比赛
            } catch (InterruptedException | BrokenBarrierException e) {
    
    
                e.printStackTrace();
            }
        }
    }

    static class LockFreeQueue<E> {
    
    
        private static class Node<E> {
    
    
            final E item;
            volatile Node<E> next;

            Node(E item) {
    
    
                this.item = item;
            }
        }

        private volatile Node<E> head;
        private volatile Node<E> tail;

        public boolean isEmpty() {
    
    
            return head == null;
        }

        public void add(E item) {
    
    
            Node<E> node = new Node<>(item);
            while (true) {
    
    
                Node<E> last = tail;
                Node<E> next = last == null ? head : last.next;
                if (last == tail) {
    
    
                    if (next == null) {
    
    
                        if (compareAndSetNext(last, null, node)) {
    
    
                            compareAndSetTail(last, node);
                            return;
                        }
                    } else {
    
    
                        compareAndSetTail(last, next);
                    }
                }
            }
        }

        public E poll() {
    
    
            while (true) {
    
    
                Node<E> first = head;
                Node<E> last = tail;
                Node<E> next = first == null ? null : first.next;
                if (first == head) {
    
    
                    if (first == last) {
    
    
                        if (next == null) {
    
    
                            return null;
                        }
                        compareAndSetTail(last, next);
                    } else {
    
    
                        E item = next.item;
                        if (compareAndSetHead(first, next)) {
    
    
                            return item;
                        }
                    }
                }
            }
        }

        private boolean compareAndSetHead(Node<E> expect, Node<E> update) {
    
    
            return unsafe.compareAndSwapObject(this, headOffset, expect, update);
        }

        private boolean compareAndSetTail(Node<E> expect, Node<E> update) {
    
    
            return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
        }

        private boolean compareAndSetNext(Node<E> node, Node<E> expect, Node<E> update) {
    
    
            return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
        }

        private static final sun.misc.Unsafe unsafe;
        private static final long headOffset;
        private static final long tailOffset;
        private static final long nextOffset;

        static {
    
    
            try {
    
    
                unsafe = getUnsafe();
                Class<?> k = LockFreeQueue.class;
                headOffset = unsafe.objectFieldOffset(k.getDeclaredField("head"));
                tailOffset = unsafe.objectFieldOffset(k.getDeclaredField("tail"));
                nextOffset = unsafe.objectFieldOffset(Node.class.getDeclaredField("next"));
            } catch (Exception e) {
    
    
                throw new Error(e);
            }
        }

        private static sun.misc.Unsafe getUnsafe() throws Exception {
    
    
            Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (sun.misc.Unsafe) field.get(null);
        }
    }
}

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-4s preparation 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 LockFreeQueue to simulate the movement of the dragon boat. Each time the dragon boat moves forward, it takes 1~10ms. 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 CAS-based Java multi-threaded program can well simulate the scene of multiple teams competing in the dragon boat race. By using CAS-based synchronization mechanisms such as Java's AtomicInteger and LockFreeQueue, we can achieve efficient thread collaboration and synchronization operations.

Guess you like

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