An article to understand the use of Java thread pool ThreadPoolExecutor

In actual projects, we may process very short execution time but a very large number of requests. At this time, if a new thread is created for each request, it may cause a performance bottleneck. Because the thread creation and destruction time may be greater than the task execution time, system performance will be greatly reduced.

JDK1.5 provides thread pool support, which uses thread reuse technology to eliminate frequent thread creation to achieve the purpose of processing a large number of concurrent requests. The principle of the thread pool is to create a "ThreadPool" with excellent operating efficiency and then manage the creation and destruction of thread objects in the pool. When using the pool, only specific tasks need to be performed, and the processing of thread objects is encapsulated in the pool. . The following is a simple example of thread pool:

public static void main(String[] args) throws IOException {
    ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 3, 30, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());
    tpe.execute(() -> {
        System.out.println("我在线程池中" + Thread.currentThread().getName() + "执行");
    });
}

In the above example, we use ThreadPoolExecutor to create a thread pool. Below we first look at the hierarchical structure diagram of the ThreadPoolExecutor class:

From the above class hierarchy diagram, we can see that Executor is the root interface of the entire thread pool. In fact, there is only one method in this interface. This method is used to specify a given command (thread task) at some point in the future. The task may be executed in a new thread, a thread existing in the thread pool, or a thread that is being called. It is determined by the class that implements Executor. The definition of Executor is as follows:

public interface Executor {
    void execute(Runnable command);
}

The ExecutorService interface inherits the Executor interface. The Executor interface only defines methods for thread pool execution tasks, and ExecutorService adds more methods about thread pools. Now you only need to know that there are these methods. These methods will be implemented in the following implementation classes. Explain in detail. The following is the definition of the ExecutorService interface:

public interface ExecutorService extends Executor {
    void shutdown();
    List<Runnable> shutdownNow();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

There is AbstractExecutorService that directly inherits or implements ExecutorService. AbstractExecutorService is an abstract implementation class of ExecutorService. It is an abstract class that implements some functions of ExecutorService and Executor. Its real implementation is ThreadPoolExecutor. Below we will introduce the use of thread pool ThreadPoolExecutor in detail.

Executor is just an interface, a specification, and does not implement any functions. We need to implement the ThreadPoolExecutor class to complete the specified functions.
ThreadPoolExecutor provides several construction methods to create ThreadPoolExecutor instances. The following is one of them. Other construction methods will eventually call this construction method.

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
}

The number of threads saved in the corePoolSize thread pool in the above parameters, including idle threads, which is the size of the core pool; maximumPoolSize is the maximum number of threads allowed in the pool; keepAliveTime When the number of threads is greater than corePoolSize, the idle thread will be deleted when it exceeds keepAliveTime; Unit The time unit of keepAliveTime; the queue used to save tasks before the workQueue is executed, our most commonly used are LinkedBlockQueue, ArrayBlockQueue and SynchronousQueue. ThreadFactory represents the thread factory, and handler represents the processing after rejection. Below we introduce the execution process of ThreadPoolExecutor.

ThreadPoolExecutor uses different queues to have different execution processes. We take LinkedBlockQueue and SynchronousQueue as examples. In order to simplify the parameters, we use code to indicate: A represents the task to be executed, B represents corePoolSize, C represents maximumPoolSize, E represents LinkedBlockQueue queue, and F represents SynchronousQueue queue, G stands for keepLiveTime.

As shown in the above flowchart, when A<=B, the number of tasks to be executed is less than or equal to corePoolSize, and the thread pool will create threads to execute tasks immediately; tasks will not be added to the queue

When A>B&&A<=C, that is, when the number of tasks executed is greater than corePoolSize but less than maximumPoolSize, the two different queues will have different processing methods. LinkedBlockQueue will put AB tasks in the LinkedBlockQueue queue, and wait until the previous tasks are executed. , Take out tasks from the queue for execution, SynchronousQueue creates new threads to perform all tasks. The demo code is as follows:

public class LinkedQueueThreadPool2 {
    public static void main(String[] args) {
	Runnable runnable = new TpeRunnable("blockQueue");
	ThreadPoolExecutor tpe = new ThreadPoolExecutor(3, 5, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
       //要执行的任务数量大于corePoolSize,但小于maximumPoolSize
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	System.out.println(tpe.getPoolSize());//3
	System.out.println(tpe.getActiveCount());//3
	System.out.println(tpe.getQueue().size());//2,两个任务放到等待列表,相当于C和G参数无效。poolSize数量一直小于等于corePoolSize。
	}
}
public class SynchronousQueueThreadPool2 {
    public static void main(String[] args) throws InterruptedException {
	Runnable runnable = new TpeRunnable("SynchQueue");
	ThreadPoolExecutor tpe = new ThreadPoolExecutor(3, 5, 5, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
        //要执行的任务数量大于corePoolSize,但小于maximumPoolSize
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
        //迅速创建新线程并执行任务
	System.out.println(tpe.getPoolSize());//5
	System.out.println(tpe.getActiveCount());//5
	System.out.println(tpe.getQueue().size());//0
	Thread.sleep(10000);
	System.out.println(tpe.getPoolSize());//3,5秒之后空闲线程清除,只剩下与corePoolSize数量一样的线程数
    }
}

When A>C, that is, when the number of tasks to be executed is greater than the maximumPoolSize, the two different queues will also have different processing methods. LinkedBlockQueue will put AB tasks in the LinkedBlockQueue queue, and SynchronousQueue will directly throw java.util.concurrent .RejectedExecutionException exception. The following is a code example for this situation:

public class LinkedQueueThreadPool3 {
    public static void main(String[] args) throws InterruptedException {
	Runnable runnable = new TpeRunnable("blockQueue");
	ThreadPoolExecutor tpe = new ThreadPoolExecutor(3, 5, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
       //该处理方式与上面的类似,大于3的任务会加入到队列等待执行
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	System.out.println(tpe.getPoolSize());//3
	System.out.println(tpe.getActiveCount());//3
	System.out.println(tpe.getQueue().size());//3
	Thread.sleep(10000);
	System.out.println(tpe.getPoolSize());//3
    }
}
public class SynchronousQueueThreadPool3 {
    public static void main(String[] args) throws InterruptedException {
	Runnable runnable = new TpeRunnable("SynchQueue");
	ThreadPoolExecutor tpe = new ThreadPoolExecutor(3, 5, 5, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	System.out.println(tpe.getPoolSize());
	System.out.println(tpe.getActiveCount());
        System.out.println(tpe.getQueue().size());
        Thread.sleep(10000);
	System.out.println(tpe.getPoolSize());
        //该示例会抛出异常,当要执行的线程数大于maximumPoolSize时,并且使用SynchronousQueue队列时,会抛出异常
    }
}

In order to express the above execution flow more clearly, I drew a flowchart to show the execution flow of the thread pool, as shown in the following figure:

Above we demonstrated the use of ThreadPoolExecutor in different queues. Below we introduce how to close ThreadPoolExecutor and how to properly close our thread pool. ThreadPoolExecutor provides shutdown(), shutdownNow() and isShutdown() three ways to close the thread pool.

isShutDown() determines whether the thread pool has been shut down. As long as the shutdown() method is called, it will return true.

The function of the shutdown() method is to make the currently unfinished thread continue to execute without performing new tasks. After calling the shutdown() method, the main thread will end immediately, and the thread pool will continue to run. It will stop until all tasks are executed. If the shutdown() method is not called, the thread pool will remain in place so that new Task tasks can be executed at any time. The Shutdown() method is non-blocking. Adding a new task after executing the shutdown() method will throw a RejectedExecutionException.

The ShutdownNow() method interrupts all tasks and throws InterruptedException. If you use Thread.currentThread().isInterrupted() == true in the thread to determine the interruption status of the current thread, the unexecuted tasks are not being executed. If there is no such judgment, the threads that are being executed in the pool are not executed until the execution is completed, and the threads that are not executed are not executed and are removed from the execution queue. Adding a new task after executing the shutdownNow() method will throw a RejectedExecutionException.

Earlier we introduced ThreadPoolExecutor roughly. You can see that its construction contains many parameters. If you use the construction method to create a ThreadPoolExecutor instance, you need to pass in multiple parameters in the construction method. This will be very troublesome to use, but the official JDK provides us with the tool class Executors for creating thread pools.

Executors can use the newCachedThreadPool() method to create an unbounded thread pool. And it can be automatically recycled by threads. The so-called borderless means that the maximum number of threads in the thread pool is Integer.MAX_VALUE. We can use the newCachedThreadPool() and newCachedThreadPool(ThreadFactory threadFactory) methods to create borderless threads. The following example uses newCachedThreadPool():

ExecutorService tpe = Executors.newCachedThreadPool();
ExecutorService tpe = Executors.newCachedThreadPool(new MyThreadFactory());
public class MyThreadFactory implements ThreadFactory {
	public Thread newThread(Runnable r) {
		return new Thread(r);
	}
}

Executors can use the newFixedThreadPool (int nThreads) and newFixedThreadPool (int nThreads, ThreadFactory threadFactory) methods to create a bounded thread pool. The number of thread pools is the value of nThreads specified in the method.

ExecutorService es = Executors.newFixedThreadPool(2);

Executors can use the newSingleThreadPool(int nThreads) and newSingleThreadPool(int nThreads, ThreadFactory threadFactory) methods to create a thread pool with only one thread. The number of thread pools is a fixed value, there is only one.

ExecutorService es = Executors.newSingleThreadExecutor();

Executors greatly facilitates the creation of thread pools. In fact, a thread pool instance is initialized internally through the construction method of ThreadPoolExecutor. The following example is the code for creating a single thread thread pool:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,    0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
    }

 

Guess you like

Origin blog.csdn.net/wk19920726/article/details/108616114