[Java] How to judge the execution of the thread pool task?

foreword

No matter in the project development or in the interview process, you will always be asked or use concurrent programming to complete a certain function in the project.

For example, a complex query cannot use one query statement to complete this function. At this time, we need to execute multiple query statements, and then assemble the results of each query and return them to the front end. In this scenario, we You must use the thread pool for concurrent queries.

PS: The most complicated query made by Brother Lei involved a total of 21 tables. After many communications with the product and the demand side, the query business was reduced from 21 tables to at least 12 tables ( Very difficult to do), then this scenario cannot be achieved using a query statement, then concurrent queries must be arranged.

1. Demand analysis

The use of the thread pool is not complicated. The trouble is how to judge that all the tasks in the thread pool have been executed? Because we have to wait for all the tasks to be executed before we can assemble and return the data, so next, let's see how to judge whether all the tasks in the thread have been executed?

2. Implementation overview

There are many ways to judge whether the tasks in the thread pool have been executed, such as the following:

Use getCompletedTaskCount() to count the tasks that have been executed, and compare them with the total tasks of the getTaskCount() thread pool. If they are equal, it means that the tasks in the thread pool have been executed, otherwise they have not been executed.
Use FutureTask to wait for all tasks to be executed, and the tasks in the thread pool will be executed.
Use CountDownLatch or CyclicBarrier to wait for all threads to finish executing before executing subsequent processes.

The specific implementation code is as follows.

3. Concrete implementation

3.1 Count the number of completed tasks

By judging the number of scheduled execution tasks and the number of completed tasks in the thread pool, it is judged whether the thread pool has been executed completely. If the number of scheduled execution tasks = the number of completed tasks, then all the tasks in the thread pool have been executed, otherwise it will not be executed. Executed.
The sample code is as follows:

private static void isCompletedByTaskCount(ThreadPoolExecutor threadPool) {
    
    
    while (threadPool.getTaskCount() != threadPool.getCompletedTaskCount()) {
    
    
    }
}

The above program execution results are as follows:
insert image description here

Method Description

getTaskCount(): Returns the total number of tasks scheduled to execute. Since the state of tasks and threads may change dynamically during computation, the returned value is only an approximation.
getCompletedTaskCount(): Returns the total number of completed execution tasks. Because the state of tasks and threads may change dynamically during computation, the returned value is only an approximation, but does not decrease between successive calls.

Disadvantage analysis
The disadvantage of this judgment method is that getTaskCount() and getCompletedTaskCount() return an approximate value, because the status of tasks and threads in the thread pool may change dynamically during the calculation process, so both of them return an approximate value.

3.2 FutureTask

The advantage of FutrueTask is that the task judgment is accurate. Calling the get method of each FutrueTask is to wait for the task to be executed, as shown in the following code:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

/**
 * 使用 FutrueTask 等待线程池执行完全部任务
 */
public class FutureTaskDemo {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        // 创建一个固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(3);
        // 创建任务
        FutureTask<Integer> task1 = new FutureTask<>(() -> {
    
    
            System.out.println("Task 1 start");
            Thread.sleep(2000);
            System.out.println("Task 1 end");
            return 1;
        });
        FutureTask<Integer> task2 = new FutureTask<>(() -> {
    
    
            System.out.println("Task 2 start");
            Thread.sleep(3000);
            System.out.println("Task 2 end");
            return 2;
        });
        FutureTask<Integer> task3 = new FutureTask<>(() -> {
    
    
            System.out.println("Task 3 start");
            Thread.sleep(1500);
            System.out.println("Task 3 end");
            return 3;
        });
        // 提交三个任务给线程池
        executor.submit(task1);
        executor.submit(task2);
        executor.submit(task3);

        // 等待所有任务执行完毕并获取结果
        int result1 = task1.get();
        int result2 = task2.get();
        int result3 = task3.get();
        System.out.println("Do main thread.");
    }
}

The execution result of the above program is as follows:
insert image description here

3.3 CountDownLatch和CyclicBarrier

CountDownLatch is similar to CyclicBarrier in that it waits for all tasks to reach a certain point before performing subsequent operations, as shown in the following figure:
insert image description here

The sample code used by CountDownLatch is as follows:

public static void main(String[] args) throws InterruptedException {
    
    
    // 创建线程池
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 20,
    	0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1024));
    final int taskCount = 5;    // 任务总数
    // 单次计数器
    CountDownLatch countDownLatch = new CountDownLatch(taskCount); // ①
    // 添加任务
    for (int i = 0; i < taskCount; i++) {
    
    
        final int finalI = i;
        threadPool.submit(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                try {
    
    
                    // 随机休眠 0-4s
                    int sleepTime = new Random().nextInt(5);
                    TimeUnit.SECONDS.sleep(sleepTime);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println(String.format("任务%d执行完成", finalI));
                // 线程执行完,计数器 -1
                countDownLatch.countDown();  // ②
            }
        });
    }
    // 阻塞等待线程池任务执行完
    countDownLatch.await();  // ③
    // 线程池执行完
    System.out.println();
    System.out.println("线程池任务执行完成!");
}

Code description: The code lines marked as ①, ②, and ③ in the above code are core implementation codes, among which:
① declares a counter containing 5 tasks;
② is the counter -1 after each task is executed;
③ is blocking Waiting for the counter CountDownLatch to decrease to 0 means that the tasks are all executed, and the business code behind the await method can be executed.

The execution result of the above program is as follows:
insert image description here

Disadvantage Analysis
The disadvantage of CountDownLatch is that the counter can only be used once, and the CountDownLatch cannot be reused after it is created.
CyclicBarrier is similar to CountDownLatch. It can be understood as a reusable cycle counter. CyclicBarrier can call the reset method to reset itself to the initial state. The specific implementation code of CyclicBarrier is as follows:

public static void main(String[] args) throws InterruptedException {
    
    
    // 创建线程池
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 20,
    	0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1024));
    final int taskCount = 5;    // 任务总数
    // 循环计数器 ①
    CyclicBarrier cyclicBarrier = new CyclicBarrier(taskCount, new Runnable() {
    
    
        @Override
        public void run() {
    
    
            // 线程池执行完
            System.out.println();
            System.out.println("线程池所有任务已执行完!");
        }
    });
    // 添加任务
    for (int i = 0; i < taskCount; i++) {
    
    
        final int finalI = i;
        threadPool.submit(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                try {
    
    
                    // 随机休眠 0-4s
                    int sleepTime = new Random().nextInt(5);
                    TimeUnit.SECONDS.sleep(sleepTime);
                    System.out.println(String.format("任务%d执行完成", finalI));
                    // 线程执行完
                    cyclicBarrier.await(); // ②
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
    
    
                    e.printStackTrace();
                }
            }
        });
    }
}

insert image description here

The execution result of the above program is as follows:

Method Description
CyclicBarrier has 3 important methods:

Construction method: The construction method can pass two parameters, parameter 1 is the number of parties of the counter, and parameter 2 is the event (method) that can be executed when the counter is 0, that is, after all tasks are executed.
await method: Blocking and waiting on the CyclicBarrier, when this method is called, the internal counter of the CyclicBarrier will be -1 until one of the following situations occurs:

When the number of threads waiting on the CyclicBarrier reaches parties, that is, the declared number of counters, all threads are released and continue to execute.
If the current thread is interrupted, an InterruptedException will be thrown, and the wait will be stopped and the execution will continue.
If other waiting threads are interrupted, the current thread throws BrokenBarrierException, stops waiting and continues execution.
When other waiting threads time out, the current thread throws BrokenBarrierException, stops waiting and continues execution.
When other threads call the CyclicBarrier.reset() method, the current thread throws BrokenBarrierException, stops waiting and continues to execute.

The reset method: returns the CyclicBarrier to its initial state. Intuitively, it does two things:

If there are waiting threads, a BrokenBarrierException will be thrown, and these threads will stop waiting and continue to execute.
Set the broken flag bit broken to false.

Analysis of advantages and disadvantages
CyclicBarrier is more complex than CountDownLatch from design complexity to use. Compared with CountDownLatch, its advantage is that it can be reused (just call reset to restore to the initial state), and the disadvantage is that it is more difficult to use. high.

summary

In the solution for judging whether the thread pool task is completed, the statistics of the way the thread pool executes the task (implementation method 1) and the implementation method 3 (CountDownLatch or CyclicBarrier) are "unnamed", and only focus on Quantity, do not pay attention to (specific) objects, so these methods may be affected by external code, so using FutureTask to wait for the completion of specific tasks is the most recommended method of judgment.

Guess you like

Origin blog.csdn.net/u011397981/article/details/132113713