Introduction to Java Concurrency Tools

Overview

In the JDK concurrency package (java.util.concurrent), we have provided us with several very important concurrency tool classes, namely CountDownLatch, CyclicBarrier, Semaphore and Exchanger.

First summary

1. CountDownLatch, it is a counter method to ensure thread synchronization; it does not control the context between multiple sub-threads, but only guarantees that a certain thread can execute after these sub-threads are executed.

2. CyclicBarrier, which enables multi-thread synchronization by setting a barrier, can control multiple threads at the barrier and other threads also execute to the barrier point, and can achieve the functions of CountDownLatch, but it is more powerful than CountDownLatch;

3. Semaphore, semaphore, used to control the number of concurrent threads that access a public resource;

4. Exchanger, used for data exchange between two threads.

 

Introduction

1)CountDownLatch

CountDownLatch, similar to a counter, is used to wait for one or more threads to complete the operation and start the execution of its own code.

Its constructor receives an integer of type int as a counter. For example, if you want to wait for N threads to finish executing, pass in N. Each time the countDown function is called, it means that a certain thread has finished executing. In fact, this N is not bound to threads, which means that it is not necessarily the same as the number of threads. It only needs to execute the countDown function N times, and the currently waiting thread will start executing. The specific code is listed below:

public static class CountDownLatchTest {
        static CountDownLatch countDownLatch = new CountDownLatch(2);
        public static void main(String[] args) {

            new Thread(new Runnable() {
                @Override
                public void run() {
                    SleepUtils.second(2);
                    System.out.println("1");
                    countDownLatch.countDown();
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    SleepUtils.second(4);
                    System.out.println("2");
                    countDownLatch.countDown();
                }
            }).start();
            try {
                // 主线程开始等待
                countDownLatch.await();

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("3");
        }
    }

The output is as follows:

1
2
3
Process finished with exit code 0

important point

1. If the passed parameter is greater than 2, then the main thread will wait forever.

2. The counter must be greater than 0. If it is 0, calling the await method will not block the current thread.

Application scenario

When encountering a relatively time-consuming task with a large amount of calculation, we can consider using multi-threading to operate, split a large task into multiple small tasks (one task is equivalent to one thread), when each small task After the task is executed and the result is returned, a certain main thread performs statistics on the result.

2)CyclicBarrier

CyclicBarrier is a synchronization barrier. Its main function is to allow a group of threads to reach a barrier (also called a synchronization point) to be blocked. Until the last thread reaches the barrier, the barrier will be opened and all intercepted threads will continue to execute .

By default, its constructor also receives an int type parameter N as the number of threads intercepted by the barrier. Each thread calls the await method to indicate that it has reached the barrier point and is then blocked. Specific examples are as follows:

public class CyclicBarrierTest {
        // 参数表示屏障拦截的线程数量, 每个线程调用 await方法,告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞
        // 屏障拦截的线程数量必须和当前的线程数一致,并且都调用await方法,否则当前所有的线程都处于等待状态
        static CyclicBarrier c = new CyclicBarrier(3);
        public static void main(String[] args) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("1---1 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                        c.await();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("1---2 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                }

            }).start();


            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("2---1 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                    try {
                        c.await();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("2---2 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                }
            }).start();


            SleepUtils.second(2);
            System.out.println("3---1 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            try {
                c.await();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("3---2 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        }
    }
The output is as follows:
1---1 17:05:01
2---1 17:05:01
3---1 17:05:03
3---2 17:05:03
1---2 17:05:03
2---2 17:05:03

Process finished with exit code 0

important point

1. The N in the constructor must be the total number of threads. When the last thread calls the await method (reaching the barrier), the barrier will be opened and the blocked thread will execute. The meaning of N here is the meaning of CountDownLatch. N is not the same.

2. We found that when all threads reach the barrier, when the barrier is opened, which thread will be executed first? The answer to the above code is uncertain. But CyclicBarrier provides us with a more advanced usage, that is, the constructor also supports passing a Runnable object. When the barrier is opened, the run method in Runnable is executed first. (This function is very powerful and can completely replace CountDownLatch)

Application scenario

Same as CountDownLatch

Difference from CountDownLatch:

The CountDownLatch counter can only be used once, while the CyclicBarrier counter can be reset using the reset method, so it is suitable for more complex business scenarios.

3)Semaphore

Semaphore is a semaphore, which is mainly used to control the number of threads that concurrently access specific resources, and to coordinate the reasonable use of common resources by each thread.

The constructor also receives an int type parameter N as an input parameter, which is used to limit the maximum number of concurrent threads that access a certain public resource, obtain a license through acquire, and release a license.

Specific examples are as follows:

public class SemaphoreTest {

        private static final int THREAD_COUNT = 6;
        private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);
        private static Semaphore semaphore = new Semaphore(2);
        public static void main(String[] args) {
            for (int i = 0; i < THREAD_COUNT; i++) {
                threadPool.execute(new MyRunnable(i + 1));
            }
            threadPool.shutdown();
        }

        static class MyRunnable implements Runnable {
            private int sleep;
            public MyRunnable(int sleep) {
                this.sleep = sleep;
            }
            @Override
            public void run() {
                try {
                    semaphore.acquire();
                    System.out.println("save data " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                    SleepUtils.second(sleep);
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
The output is as follows:
save data 19:44:37
save data 19:44:37
save data 19:44:38
save data 19:44:39
save data 19:44:41
save data 19:44:43

Process finished with exit code 0

important point

1. It can be found from the output that the concurrent number of threads is 2. When one thread finishes executing, the next thread gets the resource.

Application scenario

When we have a large number of threads completing a huge task, but a public resource has a link tree that limits the thread, we need to control the access of these large numbers of threads to this common resource. For example, when we have hundreds of threads that need to process the data files of G on the local, after each thread processing is completed, the results need to be written to the database, and the database only supports concurrent links of ten threads. At this time, we will link to the database. The maximum number of connections can be controlled through Semaphore.

4)Exchanger

Exchanger (Exchanger), it is a collaboration tool used between threads, mainly used for data exchange between threads. It provides a synchronization point at which two threads can exchange data with each other. Look at the specific demo below:

public class ExchangerTest {
        private static final Exchanger<String> exchanger = new Exchanger<>();
        private static ExecutorService threadPool = Executors.newFixedThreadPool(2);
        public static void main(String[] args) {
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    String a = "aaaaaaaaaa";
                    try {
                        String b = exchanger.exchange(a);
                        System.out.println("---" + b);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });


            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        String b = "bbbbbbbb";
                        String a = exchanger.exchange("bababa");
                        System.out.println("a is " + a + " , b is " + b);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            threadPool.shutdown();
        }
    }

The output is as follows:

---bababa
a is aaaaaaaaaa , b is bbbbbbbb

Process finished with exit code 0

important point

1. Exchanger can only act between two threads. If it acts on the third thread, the third thread has been waiting;

2. There is also an overloaded function in exchange, which receives a waiting time to avoid waiting all the time.

references

"The Art of Concurrent Programming in Java"

Guess you like

Origin blog.csdn.net/qq_27828675/article/details/114068266