[Concurrent process] Use tool classes to control concurrent processes

Using tool classes to control concurrent processes makes it easier for threads to cooperate with each other to complete business logic.

Insert picture description here

1 CountDownLatch--Door Latch

The following scenario is that when a loan application is initiated, the bank will query the customer's multiple credits, and wait until the multiple credits have checked the details before determining whether the decision is approved.

public class QueryClientCredits {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        CountDownLatch latch = new CountDownLatch(3);
        ExecutorService pool = new ThreadPoolExecutor(
                3, 3, 0, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>());
        for (int i = 0; i < 3; i++) {
    
    
            final int zxId = i + 1;
            pool.submit(() ->{
    
    
                try {
    
    
                    TimeUnit.MILLISECONDS.sleep((long) (Math.random() * 1000));
                    System.out.println("已经查到" + zxId + "资信");
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } finally {
    
    
                    latch.countDown();
                }
            });
        }
        System.out.println("等待请求资信详情...");
        latch.await();
        System.out.println("已经查到所有资信,下一步进行决策。");
    }
}

The above scene is one more wait. For first-class multiple, usually set

CountDownLatch latch = new CountDownLatch(1);

Call the await method in each thread, wait for the countdown instruction, and execute it at the same time.

summary

The typical use of CountDownLatch is one or two kinds of first-class and more-waiting, mainly in the use of the countDown() and await() methods of CountDownLatch, which can also be extended to the case of multi-waiting.

CountDownLatch cannot be rolled back and reset. If you need to recount, you can consider CyclicBarrier.

1.1 The difference between join method and CountDownLatch

There are two main differences:

  1. After calling the join method of the child thread, the thread will block until the end of the thread; while using CountDownLatch to use a counter to count, it does not necessarily wait until the end of the thread.
  2. If the thread pool is used to manage threads, the Runnable task is usually added to the thread pool, and the join method cannot be called.
    In summary, CountDownLatch has more flexible and elegant control over threads.

1.2 Internal principle

The internal class Sync of CountDownLatch inherits AQS, counts down through the state of AQS, and the decrement operation is realized by CAS.

private static final class Sync extends AbstractQueuedSynchronizer {
    
    
    private static final long serialVersionUID = 4982264981922014374L;

    Sync(int count) {
    
    
        setState(count);
    }

    int getCount() {
    
    
        return getState();
    }

    protected int tryAcquireShared(int acquires) {
    
    
        return (getState() == 0) ? 1 : -1;
    }

    protected boolean tryReleaseShared(int releases) {
    
    
        // Decrement count; signal when transition to zero
        for (;;) {
    
    
            int c = getState();
            if (c == 0)
                return false;
            int nextc = c-1;
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }
}

2 Semaphore-semaphore

The function of the semaphore is to maintain the count of a license, the thread can obtain the license, the total number of licenses is reduced by 1, and the license is returned after the thread is used up. When the number of licenses is 0, other threads are BLOCKED until a license is available.

Insert picture description here

// 初始化并指定许可证数量
public Semaphore(int permits) // 默认非公平
public Semaphore(int permits, boolean fair)

// 线程获取许可证
public void acquire() // 可以一次获取多个许可证
// 使用完释放许可证
public void release() // 获取多少个,释放同等数量

License issuance

public class SemaphoreDemo {
    
    
    private static Semaphore semaphore = new Semaphore(2);

    public static void main(String[] args) {
    
    
        ExecutorService pool = Executors.newFixedThreadPool(10);
        Task task = new Task();
        for (int i = 0; i < 10; i++) {
    
    
            pool.submit(task);
        }
        pool.shutdown();
    }

    static class Task implements Runnable {
    
    
        @Override
        public void run() {
    
    
            try {
    
    
                semaphore.acquire();
                System.out.println(Thread.currentThread().getName() + "获取到许可证");
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                System.out.println(Thread.currentThread().getName() + "释放许可证");
                semaphore.release();
            }
        }
    }
}

Use attention points:

  1. The number of acquisition and release is the same.
  2. Initialize fairness settings
  3. Acquiring and releasing licenses has no requirements for threads. Maybe A gets it and B releases it.
    1. For example, the realization of "conditional wait". Thread 1 starts to work after thread 2 completes the preparation work, and thread 1 acquires and releases after thread 2 completes the task.

3 Condition-condition object

The Condition object can be used to replace Object.wait/notify and bind to Lock.

  • Same as Object.wait, await can automatically release the Lock lock, and the lock must be held when wait is called.
  • signalAll wakes up all threads that are waiting. Signal is fair and only wakes up the thread that has been waiting the longest.

3.1 Use Condition to implement a producer-consumer queue

Producer-consumer queues can also be implemented through Object.wait/notify and blocking queues. See the Object and Blocking Queue chapters respectively.

public class ProducerConsumerByCondition {
    
    
    private int capacity = 10;
    private Queue queue = new PriorityQueue(capacity);
    private Lock lock = new ReentrantLock();
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();

    class Producer implements Runnable {
    
    

        @Override
        public void run() {
    
    
            while (true) {
    
    
                lock.lock();
                try {
    
    
                    while (queue.size() == capacity) {
    
    
                        try {
    
    
                            System.out.println("队列满,等待消费");
                            notFull.await();
                        } catch (InterruptedException e) {
    
    
                            e.printStackTrace();
                        }
                    }
                    queue.offer(1);
                    notEmpty.signalAll();
                    System.out.println("生产完一个元素,队列容量:" + queue.size());
                } finally {
    
    
                    lock.unlock();
                }
            }
        }
    }
    class Consumer implements Runnable {
    
    

        @Override
        public void run() {
    
    
            while (true) {
    
    
                lock.lock();
                try {
    
    
                    while (queue.size() == 0) {
    
    
                        System.out.println("队列空,等待生产元素");
                        notEmpty.await();
                    }
                    TimeUnit.MILLISECONDS.sleep((long) (Math.random() * 1000));
                    queue.poll();
                    notFull.signalAll();
                    System.out.println("消费一个元素,队列容量:" + queue.size());
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } finally {
    
    
                    lock.unlock();
                }
            }
        }
    }

    public static void main(String[] args) {
    
    
        ProducerConsumerByCondition demo = new ProducerConsumerByCondition();
        Thread producer = new Thread(demo.new Producer());
        Thread consumer = new Thread(demo.new Consumer());
        producer.start();
        consumer.start();
    }
}

In the realization of the scene, two Condition objects of the lock are created. The advantage is that each Condition has a separate waiting queue, call the await method, put it into the corresponding waiting queue, and then call the signal method to wake up. We can see that compared to using a Condition, the granularity of awakening is smaller and more targeted to avoid waking up unnecessary threads.

4 CyclicBarrier--Cyclic Barrier

Used to block a group of threads and construct a rendezvous. After all threads arrive, the fence is cancelled, and all threads start uniformly to perform the remaining tasks.

public class CyclicBarrierDemo {
    
    
    public static void main(String[] args) {
    
    
        // 初始化循环栅栏
        CyclicBarrier barrier = new CyclicBarrier(5, () -> {
    
    
            System.out.println("所有线程到齐,开始出发。。。");
        });
        ExecutorService pool = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
    
    
            pool.submit(new Task(i, barrier));
        }
    }

    static class Task implements Runnable {
    
    
        int id;
        CyclicBarrier barrier;

        public Task(int id, CyclicBarrier barrier) {
    
    
            this.id = id;
            this.barrier = barrier;
        }

        @Override
        public void run() {
    
    

            try {
    
    
                System.out.println("线程" + id + "到达出发点。。。");
                TimeUnit.MILLISECONDS.sleep((long) (Math.random() * 1000));
                System.out.println("线程" + id + "已到达,等待出发。。。");
                barrier.await();
                System.out.println("线程" + id + "开始出发。。。");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
    
    
                e.printStackTrace();
            }

        }
    }
}

The difference between CyclicBarrier and CountDownLatch

  1. The role is different: CyclicBarrier waits for a fixed number of threads to reach the fence position to continue execution; while CountDownLatch waits for the number of counters to be 0. That is, CyclicBarrier is for threads, and CountDownLatch is for events.
  2. The reusability is different: CyclicBarrier can be reused; CountDownLatch cannot be reset cyclically.
  3. Different features: CyclicBarrier can perform specific tasks after all threads arrive.

Sample code

https://gitee.com/dtyytop/advanced-java/tree/master/src/main/java/com/lzp/java/concurrent

Guess you like

Origin blog.csdn.net/LIZHONGPING00/article/details/114476683