Seeing that my colleagues are still randomly defining thread pool parameters, I directly encapsulate a thread pool construction tool class

Posted by baruch on Mon, 17 Jan 2022 15:08:09 +0100

background

I believe that many small partners in work will use thread pool, but the definition and use of thread pool is a complex process, because there are as many as 7 thread pool constructors alone, and the definition of each parameter is not a simple basic data structure. Just pass it in directly

Therefore, some inexperienced partners use the default thread pool provided by jdk to build tool class Executors

Executors do provide some thread pools for easy use, but they are unbounded queues. Too many tasks are prone to OOM. Alibaba specification also prohibits the use of executors to construct thread pools

Although some of Alibaba's own open source middleware do not comply with this specification, it does not prevent it from being a good specification

Get task exception log

It should also be noted that errors occur during the execution of tasks by the thread pool. The default UncaughtExceptionHandler policy is to print to the console

This is just a small problem. The troublesome problem is that when submitting a task using the submit method of the thread pool, exception information will not be printed when an exception occurs. Errors will be printed only when obtaining the task results. Some people will submit a task using the submit method when they don't need to obtain the task results, resulting in an exception and don't know. This is very stupid, Like this

ExecutorService executorService = Executors.newFixedThreadPool(1);
        executorService.submit(() -> {
            int i = 1 / 0;
        });

We don't know if there is an exception. Just look at the source code

The thread executing the task is encapsulated as a RunnableFuture object. Let's look at the run method of the RunnableFuture object

The exception thrown will be put into the outcome object, which is the result of the FutureTask object returned by the submit() method executing the get() method

Therefore, do not use the submit method if you do not need to obtain the task results in the thread

The number of threads in the thread pool does not know how to set

A design of dynamic thread pool is popular in the industry. I also wrote how to implement a dynamic thread pool before

https://weihubeats.blog.csdn.net/article/details/113751243

However, there is a calculation formula for the number of threads in the thread pool

  • I/O intensive: optimal number of threads = number of CPU cores * [1 + (I/O time consumption / CPU time consumption)]
  • CPU intensive: optimal number of threads CPU+1

The thread number setting of thread pool needs to be solved

Thread pool name

The default name of thread pool is pool-x-thread-x. it is also very inconvenient to locate the problem of thread pool on our line. We need to specify an appropriate prefix for each thread pool

Coding implementation

Based on the above problems, let's optimize our own thread pool construction tool class

First, to solve the problem of no printing error in thread pool and the definition of thread name prefix, we define a custom thread creation factory

ThreadFactoryImpl

public class ThreadFactoryImpl implements ThreadFactory {

    private final AtomicLong threadIndex = new AtomicLong(0);
    private final String threadNamePrefix;
    private final boolean daemon;

    public ThreadFactoryImpl(final String threadNamePrefix) {
        this(threadNamePrefix, false);
    }

    public ThreadFactoryImpl(final String threadNamePrefix, boolean daemon) {
        this.threadNamePrefix = threadNamePrefix;
        this.daemon = daemon;
    }

    @Override
    public Thread newThread(@NotNull Runnable r) {
        Thread thread = new Thread(r, threadNamePrefix + this.threadIndex.incrementAndGet());
        thread.setDaemon(daemon);
        return thread;
    }
    
}

Then define a thread pool tool class and related tool methods

ThreadPoolUtil

public static ThreadFactory createThreadFactory(String threadNamePrefix, boolean daemon) {
        if (threadNamePrefix != null) {
            return new ThreadFactoryImpl(threadNamePrefix, daemon);
        }

        return Executors.defaultThreadFactory();

    }

Next is the core implementation of the custom thread pool construction tool class

ThreadPoolBuilder

public class ThreadPoolBuilder {

    private static final RejectedExecutionHandler defaultRejectHandler = new ThreadPoolExecutor.AbortPolicy();

    /**
     * cpu Kernel number
     */
    private static final int CPU = SystemUtil.getCPU();


    /**
     * create io ThreadPoolExecutor
     *
     * @return ThreadPoolExecutor
     */
    public static IOThreadPoolBuilder ioThreadPoolBuilder() {
        return new IOThreadPoolBuilder();
    }

    /**
     * create cpu ThreadPoolExecutor
     *
     * @return
     */
    public IOThreadPoolBuilder CPUPool() {
        return new IOThreadPoolBuilder();

    }


    public static class IOThreadPoolBuilder {

        private ThreadFactory threadFactory;

        private RejectedExecutionHandler rejectHandler;


        private int queueSize = -1;

        private int maximumPoolSize = CPU;

        private int keepAliveTime = 120;

        private boolean daemon = false;

        private String threadNamePrefix;



        public int getCorePooSize(int ioTime, int cpuTime) {
            return CPU + (1 + (ioTime / cpuTime));
        }

        public IOThreadPoolBuilder setThreadNamePrefix(String threadNamePrefix) {
            this.threadNamePrefix = threadNamePrefix;
            return this;
        }

        public IOThreadPoolBuilder setDaemon(boolean daemon) {
            this.daemon = daemon;
            return this;
        }



        public IOThreadPoolBuilder setRejectHandler(RejectedExecutionHandler rejectHandler) {
            this.rejectHandler = rejectHandler;
            return this;
        }

        public IOThreadPoolBuilder setQueueSize(int queueSize) {
            this.queueSize = queueSize;
            return this;
        }

        public IOThreadPoolBuilder setMaximumPoolSize(int maximumPoolSize) {
            this.maximumPoolSize = maximumPoolSize;
            return this;
        }

        public IOThreadPoolBuilder setKeepAliveTime(int keepAliveTime) {
            this.keepAliveTime = keepAliveTime;
            return this;
        }


        public ThreadPoolExecutor builder(int ioTime, int cpuTime) {
            BlockingQueue<Runnable> queue;

            if (rejectHandler == null) {
                rejectHandler = defaultRejectHandler;
            }
            threadFactory = ThreadPoolUtil.createThreadFactory(this.threadNamePrefix, this.daemon);

            queue = queueSize < 1 ? new LinkedBlockingQueue<>() : new ArrayBlockingQueue<>(queueSize);

            return new ThreadPoolExecutor(getCorePooSize(ioTime, cpuTime), maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS, queue, threadFactory, rejectHandler);

        }


    }
    
}

Here, you can impose a mandatory limit on the number of queues. If it is not specified, you can also report an error

use

ThreadPoolExecutor executor = ThreadPoolBuilder
                .ioThreadPoolBuilder()
                .setThreadNamePrefix("io-test")
                .setMaximumPoolSize(20)
                .builder(10, 20);

        executor.execute(() -> {
            System.out.println(Thread.currentThread().getName());
            int i = 1 / 0;
        });

summary

We can see that the creation of thread pool is still relatively complex, but we can encapsulate some thread pools suitable for our own development. For example, thread pool of IO type, thread pool of CPU type, create CachedThreadPool, etc

Topics: Java Back-end Multithreading