[Java thread pool] ExecutorService|CompletionService difference and selection

During this period of time, I did a performance test on the business system, which used more thread pool technologies, so I will make a technical summary.

There are a lot of content in this summary, mainly four:

  1. ExecutorService
  2. CompletionService
  3. Runnable
  4. Callable

The first two are thread pool related interfaces, and the latter two are multithreading related interfaces. At the end, I will explain which interface to use under what circumstances, and how to use these two types of interfaces together.

Tips: My humble opinion, please correct me if I am wrong.

一、ExecutorService

ExecutorService is an interface inherited from Executor. ExecutorService provides some common operations and methods, but ExecutorService is an interface and cannot be instantiated.
However, Java provides a helper class Executors, which can quickly obtain an ExecutorService object and use some methods of the ExecutorService interface.
ExecutorService
The Executors helper class provides multiple methods for constructing thread pools, which are commonly used in two categories:

  1. directly executed
    • newCachedThreadPool
    • newFixedThreadPool
    • newSingleThreadExecutor
  2. Delayed or scheduled execution
    • newScheduledThreadPool
    • newSingleThreadScheduledExecutor

Executors provide a thread factory overload for each method.

(1) newCachedThreadPool

Create a default thread pool object, the threads in it are reused, and are only created when they are used for the first time. It can be understood as a thread priority mode, to create a thread one by one, and not to process other tasks until the thread processing is completed.
Code:

package com.macro.boot.javaBuiltThreadPool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

public class MyExecutorService {
    
    
    public static void main(String[] args) {
    
    
        // 1. 使用帮助类
//        ExecutorService executorService = Executors.newCachedThreadPool();

        // 2. 提交任务
/*        for (int i = 0; i < 20; i++) {
            executorService.submit(new MyRunnable(i));
        }*/

        // 3. 重载方法测试
        test();
    }

    private static void test() {
    
    
        // 1. 使用帮助类
        ExecutorService executorService = Executors.newCachedThreadPool(
                new ThreadFactory() {
    
    
                    int n = 1;

                    @Override
                    public Thread newThread(Runnable r) {
    
    
                        return new Thread(r, "线程正在执行 --->" + n++);
                    }
                }
        );

        // 2. 提交任务
        for (int i = 0; i < 20; i++) {
    
    
            executorService.submit(new MyRunnable(i));
        }
    }
}

/**
 * 1. 线程类
 */
class MyRunnable implements Runnable {
    
    
    private int id;

    public MyRunnable(int id) {
    
    
        this.id = id;
    }

    @Override
    public void run() {
    
    
        String name = Thread.currentThread().getName();
        System.out.println(name + "正在执行..." + "--->" + id);
    }
}

Output: Executed almost at once, newCachedThreadPool will create threads that match the number of tasks equally until the thread that has completed the task can process the new task.

(2) newFixedThreadPool

Code: Create a thread pool with a fixed number of threads that can be reused

package com.macro.boot.javaBuiltThreadPool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

/**
 * 创建一个可固定重用次数的线程池
 */
public class MyNewFixedThreadPool {
    
    
    public static void main(String[] args) {
    
    
/*        // nThreads:线程数量
        ExecutorService es = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            es.submit(new MyRunnable(i));
        }*/
        test();
    }

    private static void test() {
    
    
        ExecutorService es = Executors.newFixedThreadPool(5, new ThreadFactory() {
    
    
            int n = 1;

            @Override
            public Thread newThread(Runnable r) {
    
    
                return new Thread(r, "线程" + n++);
            }
        });
        // 提交任务
        for (int i = 0; i < 10; i++) {
    
    
            es.submit(new MyRunnable(i));
        }
    }
}

(三)newSingleThreadExecutor

Only one thread (thread safe)

package com.macro.boot.javaBuiltThreadPool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

public class MyNewSingleThreadExecutor {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
/*        ExecutorService es = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            es.submit(new MyRunnable(i));
        }*/
        test();
    }

    private static void test() throws InterruptedException {
    
    
        ExecutorService es = Executors.newSingleThreadExecutor(new ThreadFactory() {
    
    
            int n = 1;

            @Override
            public Thread newThread(Runnable r) {
    
    
                return new Thread(r, "线程" + n++);
            }
        });
        for (int i = 0; i < 10; i++) {
    
    
            Thread.sleep(100);
            es.submit(new MyRunnable(i));
        }
    }
}

(四)newScheduledThreadPool

How to understand the delay time of this thread pool? Very simple, the start time of the first execution, plus the delay time, is the time of the second execution.

package com.macro.boot.ScheduledExecutorService;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class MyScheduledExecutor {
    
    
    public static void main(String[] args) {
    
    
        ScheduledExecutorService sec = Executors.newScheduledThreadPool(4);
        for (int i = 0; i < 10; i++) {
    
    
            sec.schedule(new MyRunnable(i), 1, TimeUnit.SECONDS);
        }
        System.out.println("开始执行。。。");
        sec.shutdown();
    }
}

class MyRunnable implements Runnable {
    
    
    private int id;

    @Override
    public String toString() {
    
    
        return "MyRunnable{" +
                "id=" + id +
                '}';
    }

    public MyRunnable(int id) {
    
    
        this.id = id;
    }

    @Override
    public void run() {
    
    
        String name = Thread.currentThread().getName();
        System.out.println(name + "执行了任务" + id);
    }
}

(五)newSingleThreadScheduledExecutor

The difference between newSingleThreadScheduledExecutor and newScheduledThreadPool is that the second execution time of newSingleThreadScheduledExecutor is equal to the first execution time, plus the time spent executing the thread, plus the delay time, which is equal to the second execution time.

Two, Completion Service

CompletionService is an interface.
When we use ExecutorService to start multiple Callables, each Callable returns a Future, and when we execute the Future's get method to get the result, the thread will be blocked until the result is obtained.
CompletionService is just to solve this problem. It is a new interface of Java8, and its implementation class is ExecutorCompletionService. The CompletionService will sort the execution results of the tasks in the thread pool in the order of execution completion, and the tasks that are completed first can be obtained first.
Code:

package com.macro.boot.completions;

import java.util.concurrent.*;

public class CompletionBoot {
    
    
    public static void main(String[] args) throws InterruptedException, ExecutionException {
    
    
        // 实例化线程池
        ExecutorService es = Executors.newCachedThreadPool();
        ExecutorCompletionService<Integer> ecs = new ExecutorCompletionService<>(es);

        for (int i = 0, j = 3; i < 20; i++) {
    
    
            ecs.submit(new CallableExample(i, j));
        }
        for (int i = 0; i < 20; i++) {
    
    
            // take:阻塞方法,从结果队列中获取并移除一个已经执行完成的任务的结果,如果没有就会阻塞,直到有任务完成返回结果。
            Integer integer = ecs.take().get();
            // 从结果队列中获取并移除一个已经执行完成的任务的结果,如果没有就会返回null,该方法不会阻塞。
            // Integer integer = ecs.poll().get();
            System.out.println(integer);
        }
        // 不要忘记关闭线程池
        es.shutdown();
    }
}
class CallableExample implements Callable<Integer> {
    
    
    /**
     * 使用构造方法获取变量
     * */
    private int a;
    private int b;

    public CallableExample(int a, int b) {
    
    
        this.a = a;
        this.b = b;
    }

    @Override
    public Integer call() throws Exception {
    
    
        return a + b;
    }

    @Override
    public String toString() {
    
    
        return "CallableExample{" +
                "a=" + a +
                ", b=" + b +
                '}';
    }
}

Three, Runnable

Both Runnable and Callable are interfaces, but there are differences:

  1. Task threads that implement the Callable interface can return execution results; task threads that implement the Runnable interface cannot return results; (emphasis)
  2. The call() method of the Callable interface allows exceptions to be thrown; while the exceptions of the run() method of the Runnable interface can only be digested internally and cannot continue to be thrown;

Code:

class MyRunnable02 implements Runnable {
    
    
    private int i;

    public MyRunnable02(int i) {
    
    
        this.i = i;
    }

    @Override
    public void run() {
    
    
        String name = Thread.currentThread().getName();
        System.out.println(name + "执行了... ---> " + i);
    }

    @Override
    public String toString() {
    
    
        return "MyRunnable{" +
                "i=" + i +
                '}';
    }
}

4. Callable

Code:

class CallableExample implements Callable<Integer> {
    
    
    /**
     * 使用构造方法获取变量
     * */
    private int a;
    private int b;

    public CallableExample(int a, int b) {
    
    
        this.a = a;
        this.b = b;
    }

    @Override
    public Integer call() throws Exception {
    
    
        return a + b;
    }

    @Override
    public String toString() {
    
    
        return "CallableExample{" +
                "a=" + a +
                ", b=" + b +
                '}';
    }
}

5. Example

This demo: Use the thread pool to query the database 500 times in a loop.
At the very beginning, ExecutorServer + Future.get was used (because querying the database definitely needs to get results, so Callable must be used and get the result set). But the blocking operation of get really affects the speed too much. Although two methods have been considered to solve it, they are all gone.
Code: (Only post the code of the thread pool, the thread class and the class that obtains the connection will not be released)

private void executorServerStart() throws SQLException, ClassNotFoundException, ExecutionException, InterruptedException {
    
    
        // get con
        TDConUtils tdConUtils = new TDConUtils();
        Connection con = tdConUtils.getCon();
        Statement statement = con.createStatement();

        // SQL
        String sql = "select last_row(value_double) from db1.tb1;";

        // ThreadPool
        ExecutorService es = Executors.newCachedThreadPool();

        // for each
        int count = 500;
        for (int i = 0; i < count; i++) {
    
    
            Future<ResultSet> submit = es.submit(new MyThread(i, con, sql));
            ResultSet resultSet = submit.get();
            // print
            while (resultSet.next()) {
    
    
                System.out.printf("输出:时间:%s,值:%f \n", resultSet.getTimestamp(1)
                        , resultSet.getDouble(2));
            }
        }
        es.shutdown();

        // close resources
        tdConUtils.close(con, statement);
    }

Running time: 8000ms +
CompletionService:
Code:

private void completionServerStart() throws SQLException, ClassNotFoundException, InterruptedException, ExecutionException {
    
    
        // get con
        TDConUtils tdConUtils = new TDConUtils();
        Connection con = tdConUtils.getCon();
        Statement statement = con.createStatement();

        // SQL
        String sql = "select last_row(value_double) from db1.tb1;";

        // ThreadPool
        ExecutorService es = Executors.newCachedThreadPool();

        //构建ExecutorCompletionService,与线程池关联
        ExecutorCompletionService<ResultSet> ecs = new ExecutorCompletionService<ResultSet>(es);
        // for each
        int count = 500;

        for (int i = 0; i < count; i++) {
    
    
            ecs.submit(new MyThread(i, con, sql));
        }
        for (int i = 0; i < count; i++) {
    
    
            // 通过take获取Future结果,此方法会阻塞
            ResultSet resultSet = ecs.take().get();
            while (resultSet.next()) {
    
    
                System.out.printf("输出:时间:%s,值:%f \n", resultSet.getTimestamp(1)
                        , resultSet.getDouble(2));
            }
        }

        es.shutdown();
        tdConUtils.close(con, statement);
    }

Running time: 300+ms

6. Use summary

According to the situation.
If you need to get the result: the thread uses Callable;
if you need to get the result asynchronously: the thread pool uses CompletionService.
If you don't need to get the result: the thread uses Runnable;
if you need to block the result: the thread pool uses ExecutorService.

Guess you like

Origin blog.csdn.net/qq_44491709/article/details/119756525