Custom Thread Pool

Keywords: Java

Introduction to Thread Pools

  • Java threads correspond to the threads of the system, and creating threads is expensive in system resources.

  • Too many threads consume more system resources, and context switching of threads incurs system resource overhead.

  • Thread pools can unify thread management, balance the direct relationship between threads and system resources, and improve system resource utilization and program stability.

  • Create a custom thread pool code as follows:

    // Create a thread pool with specified parameters
    ThreadPoolExecutor executor = new ThreadPoolExecutor(
                    1, // Maximum Number of Core Threads
                    2, // Maximum Threads = Number of Core Threads + Number of Temporary Threads
                    1000, // Maximum idle time for temporary threads beyond which they will be destroyed
                    TimeUnit.MILLISECONDS, // Unit of time
                    new ArrayBlockingQueue<Runnable>(1), // Container for task queues
                    new MyThreadFactory(), // Thread Create Factory Class
                    new MyRejectedExecutionHandler() // Task Rejection Processing Policy
            );
    
    // Submit a task to the thread pool
    executor.submit(new MyRunner("hello"));
    

Thread pool related parameters

  • corePoolSize: Maximum number of core threads, resident threads.

  • maximumPoolSize: Maximum number of threads, temporary threads.

  • keepAliveTime: The maximum idle time of a temporary thread beyond which the temporary thread will be destroyed.

  • Unit: unit between when idle.

  • workQueue: A blocked queue for storing tasks. JDK provides:

    • LinkedBlockingQueue: Chain list queue with maximum length of int type.
    • SynchronousQueue: A forward queue of 0 length that forwards only to tasks and does not store tasks.
    • DelayedWorkQueue: Delayed queue, data structure is heap, sorted by task delay time.
  • threadFactory: Threads create factory classes that can be created by implementing the java.util.concurrent.ThreadFactory interface.

  • handler: Task rejection policy, a policy executed when a thread pool is unable to process a task. You can create your own policy by implementing the java.util.concurrent.RejectedExecutionHandler interface.

    The strategies provided by JDK are as follows:

    • AbortPolicy: An exception is thrown when a task is rejected.
    • DiscardPolicy: There is no hint when rejecting a task and there is a risk of data loss.
    • DiscardOldestPolicy: Discard the task with the longest survival time when rejecting it.
    • CallerRunsPolicy: When a task is rejected, the task is given to the submitted thread for execution, which can slow down the submission speed of the task submission thread and reduce thread pool pressure.

Thread pool task execution

  • The first task committed to the thread pool is executed by the core thread.
  • When a threaded task reaches the maximum number of core threads, it is committed to the task queue store.
  • When the task queue is full, temporary threads continue to be created to perform tasks until the maximum number of threads is reached.
  • When the maximum number of threads is reached for task execution, the thread pool can no longer receive new tasks and will enter the denial policy execution.
  • When the number of task executions decreases, temporary threads are destroyed based on their idle time, reducing resource consumption.

JDK Built-in Thread Pool

Thread pool implementation classBlocking Task Queue UsedThread pool characteristicsUse precautions
Executors.newFixedThreadPool(5);LinkedBlockingQueueFixed number of threads, maximum task queue capacity intTask queue may cause OOM
Executors.newCachedThreadPool();SynchronousQueueMaximum number of core threads in int, task queue for forwarding onlyToo many threads may cause OOM
Executors.newSingleThreadExecutor();LinkedBlockingQueueNumber of core threads is one. Thread exceptions create new threads to execute tasks, ensuring the order in which tasks are executedTask queue may cause OOM
Executors.newScheduledThreadPool(5);DelayedWorkQueueSpecify frequency to execute tasks, multiple core threads to executeTask queue may cause OOM
Executors.newSingleThreadScheduledExecutor();DelayedWorkQueueSpecify frequency to execute tasks, single core thread to executeTask queue may cause OOM

Number of Threads Selection

  • JDK built-in threads have a variety of flaws, and we need to customize our own thread pool to suit specific circumstances.

  • CPU-intensive tasks recommend twice as many core threads as CPU cores, and too many threads can cause frequent thread context switching, which reduces execution efficiency.

  • IO-intensive tasks recommend that the number of thread cores be several times the number of cpu cores, since IO reads are relatively slow and more threads can increase cpu utilization.

  • Computing methods recommended by Brain Goetz, author of Java Concurrent Programming Practice:

    Number of threads = CPU Number of cores *( 1 + Average wait time / Average working time)
    
  • Too many or too few threads can affect the speed of task execution, so we can categorize tasks and execute them according to different thread pools. The coordination of system resources should also be noted.

Custom Thread Pool

package cn.devzyh.learning.threadpool;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Custom Thread Pool Parameters
 */
public class ThreadPoolParamTest {

    /**
     * Implement your own thread to create a factory class
     * It can also be created from third-party tool classes, such as:
     * Implemented through the Builder of com.google.common.util.concurrent.ThreadFactory
     */
    static class MyThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        public MyThreadFactory() {
            this.namePrefix = "TestPool-" +
                    poolNumber.getAndIncrement() +
                    "-TestThread-";
        }

        @Override
        public Thread newThread(Runnable r) {
            // Specify a custom thread name
            return new Thread(r, this.namePrefix + threadNumber.getAndIncrement());
        }
    }

    /**
     * Implement your own Task Denial Handling Policy
     * Tasks that exceed thread pool processing power will execute this policy
     * Java There are four built-in rejection strategies:
     * AbortPolicy: Throw an exception for AbortExecutionException
     * DiscardPolicy: Discard newly submitted tasks without any notifications
     * DiscardOldestPolicy: Discard the task at the head of the task queue without any notification
     * CallRunsPolicy: Deleting a task to a thread submitting it allows it to execute and delays the submission of a new task
     */
    static class MyRejectedExecutionHandler implements RejectedExecutionHandler {

        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            System.out.println("Thread pool full, retry task in 1 second");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            executor.submit(r); // retry
            System.out.println("Task to resubmit to thread pool");
        }
    }

    /**
     * Thread Tasks
     */
    static class MyRunner implements Runnable {
        private String taskName;

        public MyRunner(String name) {
            this.taskName = name;
        }

        @Override
        public void run() {

            System.out.println("Start Task Execution[" + this.taskName + "]´╝îCurrent thread name:" +
                    Thread.currentThread().getName());
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Task Execution Completed[" + this.taskName + "]");
        }

        public String getTaskName() {
            return this.taskName;
        }
    }

    public static void main(String[] args) {
        // Create a thread pool of custom parameters
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                1, // Maximum Number of Core Threads
                2, // Maximum Threads = Number of Core Threads + Number of Temporary Threads
                1000, // Maximum idle time for temporary threads beyond which they will be destroyed
                TimeUnit.MILLISECONDS, // Unit of time
                new ArrayBlockingQueue<Runnable>(1), // Container for task queues
                new MyThreadFactory(), // Thread Create Factory Class
                new MyRejectedExecutionHandler() // Task Rejection Processing Policy
        );

        executor.submit(new MyRunner("core")); // A core thread is created to perform the task
        executor.submit(new MyRunner("queue")); // At this point, the number of core threads reaches its maximum, and the current task is submitted to the task queue to wait
        executor.submit(new MyRunner("temp")); // When the task queue is full, a temporary thread will be created to execute the task
        executor.submit(new MyRunner("reject")); // The thread's ability to execute tasks at this time is exceeded to execute the task rejection policy

        executor.shutdown(); // Close Thread Pool
    }
}

Posted by phpcode on Sat, 04 Dec 2021 09:18:22 -0800