The java thread pool ThreadPoolExecutor class uses

Keywords: Programming Java JDK less

 

In the Alibaba java Development Manual, it is pointed out that thread resources must be provided through thread pools, which do not allow the creation of threads displayed by the application itself. On the one hand, the creation of threads is more standardized and the number of open threads can be reasonably controlled. On the other hand, the detailed management of threads is handled by thread pools, which optimizes the overhead of resources.Thread pools are not allowed to be created using Executors, but rather through ThreadPoolExecutor. This is due to the limitations of the Executor framework in jdk that provides methods to create thread pools, such as newFixedThreadPool(), newSingleThreadExecutor(), newCachedThreadPool(), etc., but is not flexible enough; additionally, ThreadPoolExec is also used internally by the previous methodsUtor implementation, using ThreadPoolExecutor helps you clarify the rules of the thread pool, create a thread pool that meets your business scenario needs, and avoid the risk of resource exhaustion.

Here is a detailed overview of how ThreadPoolExecutor is used.

First, look at the constructor for ThreadPoolExecutor

    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.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

The constructor parameters have the following meanings:

corePoolSize: Specifies the number of threads in the thread pool, which determines whether the added task opens a new thread to execute or is placed in the workQueue task queue;

maximumPoolSize: Specifies the maximum number of threads in the thread pool. This parameter determines the maximum number of threads that the thread pool will open based on the type of workQueue task queue you are using.

keepAliveTime: When the number of idle threads in the thread pool exceeds corePoolSize, how long will the extra threads be destroyed?

unit:keepAliveTime's unit

workQueue: Task queue, which is added to the thread pool but has not yet been executed; it is generally divided into direct submit queue, bounded task queue, bounded task queue, priority task queue.

threadFactory: Thread factory, used to create threads, usually by default;

handler: Rejection policy; how to reject a task when there are too many tasks to process;

Next, we will learn more about some of the more important parameters:

1. WorQueue Task Queue

As we have already described above, it is generally divided into direct submission queue, bounded task queue, bounded task queue, priority task queue.

1. Direct Submission Queue: Set to SynchronousQueue Queue, SynchronousQueue is a special BlockingQueue. It has no capacity and will block without performing an insert operation. Another delete operation is required to wake up. Otherwise, each delete operation will wait for the corresponding insert operation.

public class ThreadPool {
    private static ExecutorService pool;
    public static void main( String[] args )
    {
        //maximumPoolSize is set to 2, rejection policy is AbortPolic y policy, throw exception directly
        pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        for(int i=0;i<3;i++) {
            pool.execute(new ThreadTask());
        }   
    }
}

public class ThreadTask implements Runnable{
    
    public ThreadTask() {
        
    }
    
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

The output is

pool-1-thread-1
pool-1-thread-2
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.hhxx.test.ThreadTask@55f96302 rejected from java.util.concurrent.ThreadPoolExecutor@3d4eac69[Running, pool size = 2, active threads = 0, queued tasks = 0, completed tasks = 2]
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.reject(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.execute(Unknown Source)
    at com.hhxx.test.ThreadPool.main(ThreadPool.java:17)

You can see that when the task queue is SynchronousQueue and the number of threads created is greater than maximumPoolSize, the rejection policy is executed directly and an exception is thrown.

With the SynchronousQueue queue, submitted tasks are not saved and are always immediately committed for execution.If the number of threads used to execute a task is less than maximumPoolSize, try creating a new process, and if the maximum value set by maximumPoolSize is reached, execute a rejection policy based on the handler you set.In this case, you need to have an accurate assessment of the concurrency of your program in order to set the appropriate number of maximumPoolSize, otherwise the rejection policy will be easily executed.

2. Bounded task queues: Bounded task queues can be implemented using ArrayBlockingQueue as follows

pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

With the ArrayBlockingQueue bounded task queue, if new tasks need to be executed, the thread pool creates new threads until the number of threads created reaches corePoolSize, which adds new tasks to the waiting queue.If the wait queue is full, which exceeds the capacity initialized by ArrayBlockingQueue, continue creating threads until the number of threads reaches the maximum number set by maximumPoolSize and if it is larger than maximumPoolSize, a deny policy is executed.In this case, the upper limit on the number of threads is directly related to the state of the bounded task queue. If the bounded queue has a large initial capacity or is not overloaded, the number of threads will remain below corePoolSize; otherwise, when the task queue is full, the maximum umPoolSize will be the upper limit on the number of threads.

3. Unbounded task queues: Bounded task queues can be implemented using LinkedBlockingQueue as follows

pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

With an unbounded task queue, the task queue of the thread pool can add new tasks indefinitely, and the maximum number of threads created by the thread pool is the number set by your corePoolSize, which means that the maximumPoolSize parameter is invalid in this case, even if you have cached many unexecuted tasks in your task queue, when the number of threads in the thread pool reaches corePoolSizeWhen using this task queue mode, be aware of the coordination and control between the submission and processing of your tasks. Otherwise, the tasks in the queue will continue to grow because they cannot be handled in a timely manner until the last resource is exhausted.

4. Priority Queue: Priority Queue is implemented by Priority BlockingQueue. Here is an example to illustrate

public class ThreadPool {
    private static ExecutorService pool;
    public static void main( String[] args )
    {
        //Priority Queue
        pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new PriorityBlockingQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
          
        for(int i=0;i<20;i++) {
            pool.execute(new ThreadTask(i));
        }    
    }
}

public class ThreadTask implements Runnable,Comparable<ThreadTask>{
    
    private int priority;
    
    public int getPriority() {
        return priority;
    }

    public void setPriority(int priority) {
        this.priority = priority;
    }

    public ThreadTask() {
        
    }
    
    public ThreadTask(int priority) {
        this.priority = priority;
    }

    //When the current object is compared with other objects, it returns -1 if the current priority is higher, 1 if the priority is lower, and the lower the value, the higher the priority
    public int compareTo(ThreadTask o) {
         return  this.priority>o.priority?-1:1;
    }
    
    public void run() {
        try {
            //Blocking threads to put subsequent tasks into the cache queue
            Thread.sleep(1000);
            System.out.println("priority:"+this.priority+",ThreadName:"+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    
    }
}

Let's look at the results of the execution

priority:0,ThreadName:pool-1-thread-1
priority:9,ThreadName:pool-1-thread-1
priority:8,ThreadName:pool-1-thread-1
priority:7,ThreadName:pool-1-thread-1
priority:6,ThreadName:pool-1-thread-1
priority:5,ThreadName:pool-1-thread-1
priority:4,ThreadName:pool-1-thread-1
priority:3,ThreadName:pool-1-thread-1
priority:2,ThreadName:pool-1-thread-1
priority:1,ThreadName:pool-1-thread-1

 

You can see that, except for the first task, which creates a thread directly for execution, all other tasks are queued for priority tasks, rearranged by priority, and the number of threads in the thread pool has been corePoolSize, that is, there is only one.

From the code running, we can see that PriorityBlockingQueue is actually a special boundless queue. No matter how many tasks are added, the thread pool will not create more threads than corePoolSize. However, other queues generally process tasks according to FIFO rules, while PriorityBlockingQueue queues can customize rules based on tasks.Priority order is executed one after another.

2. Rejection Policy

In general, when we create a thread pool, the task queue will choose to create a bounded task queue in order to prevent resource exhaustion, but in this mode, if the task queue is full and the number of threads created by the thread pool reaches the maximum number of threads you have set, you will need to specify the RejectedExecutionHandler parameter of ThreadPoolExecutor, which is a reasonable rejection policy, to handle the thread pool "super"Case of loading.ThreadPoolExecutor's own rejection policy is as follows:

1. AbortPolicy policy: This policy throws exceptions directly to prevent the system from working properly;

2. CallerRunsPolicy policy: If the maximum number of threads in the thread pool is reached, the policy will put tasks in the task queue running in the caller thread;

3. DiscardOledestPolicy: This policy discards the oldest task in the task queue, that is, the task that was first added to the current task queue and is about to be executed immediately, and tries to submit it it again;

4. DiscardPolicy Policy: This policy silently discards unprocessable tasks and does nothing.Of course, with this strategy, you need to allow the loss of tasks in business scenarios;

The above built-in strategies all implement the RejectedExecutionHandler interface, but you can also extend the RejectedExecutionHandler interface yourself to define your own rejection policy. Let's look at the sample code:

public class ThreadPool {
    private static ExecutorService pool;
    public static void main( String[] args )
    {
        //Custom Rejection Policy
        pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5),
                Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                System.out.println(r.toString()+"Rejection Policy Executed");
                
            }
        });
          
        for(int i=0;i<10;i++) {
            pool.execute(new ThreadTask());
        }    
    }
}

public class ThreadTask implements Runnable{    
    public void run() {
        try {
            //Blocking threads to put subsequent tasks into the cache queue
            Thread.sleep(1000);
            System.out.println("ThreadName:"+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    
    }
}

Output results:

com.hhxx.test.ThreadTask@33909752 Rejection Policy Executed
com.hhxx.test.ThreadTask@55f96302 Rejection Policy Executed
com.hhxx.test.ThreadTask@3d4eac69 Rejection Policy Executed
ThreadName:pool-1-thread-2
ThreadName:pool-1-thread-1
ThreadName:pool-1-thread-1
ThreadName:pool-1-thread-2
ThreadName:pool-1-thread-1
ThreadName:pool-1-thread-2
ThreadName:pool-1-thread-1

You can see that because tasks add hibernation blockade, execution takes time, resulting in certain tasks being discarded, thus executing a custom rejection policy;

3. ThreadFactory Custom Thread Creation

Threads in the thread pool are created through ThreadFactory in ThreadPoolExecutor.By customizing the ThreadFactory, you can set some special settings for threads created in the thread pool as needed, such as naming, priority, etc. The following code records and names threads created in the thread pool through ThreadFactory

public class ThreadPool {
    private static ExecutorService pool;
    public static void main( String[] args )
    {
        //Custom Thread Factory
        pool = new ThreadPoolExecutor(2, 4, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5),
                new ThreadFactory() {
            public Thread newThread(Runnable r) {
                System.out.println("thread"+r.hashCode()+"Establish");
                //Thread Naming
                Thread th = new Thread(r,"threadPool"+r.hashCode());
                return th;
            }
        }, new ThreadPoolExecutor.CallerRunsPolicy());
          
        for(int i=0;i<10;i++) {
            pool.execute(new ThreadTask());
        }    
    }
}

public class ThreadTask implements Runnable{    
    public void run() {
        //Name of output execution thread
        System.out.println("ThreadName:"+Thread.currentThread().getName());
    }
}

Let's look at the output

Thread 118352462 created
//Thread 1550089733 Creation
//Thread 865113938 Created
ThreadName:threadPool1550089733
ThreadName:threadPool118352462
//Thread 1442407170 Created
ThreadName:threadPool1550089733
ThreadName:threadPool1550089733
ThreadName:threadPool1550089733
ThreadName:threadPool865113938
ThreadName:threadPool865113938
ThreadName:threadPool118352462
ThreadName:threadPool1550089733
ThreadName:threadPool1442407170

You can see in the thread pool that we record output and name for each thread created.

4. ThreadPoolExecutor Extension

The ThreadPoolExecutor extension is mainly implemented around three interfaces, beforeExecute(), afterExecute(), and terminated().

1. beforeExecute: Execute tasks in the thread pool before they run

2. afterExecute: Execute after a task in the thread pool has finished running

3. terminated: executed after the thread pool exits

These three interfaces allow us to monitor the start and end times of each task, or some other functionality.Here's what we can do with the code

public class ThreadPool {
    private static ExecutorService pool;
    public static void main( String[] args ) throws InterruptedException
    {
        //Implement custom interfaces
        pool = new ThreadPoolExecutor(2, 4, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5),
                new ThreadFactory() {
            public Thread newThread(Runnable r) {
                System.out.println("thread"+r.hashCode()+"Establish");
                //Thread Naming
                Thread th = new Thread(r,"threadPool"+r.hashCode());
                return th;
            }
        }, new ThreadPoolExecutor.CallerRunsPolicy()) {
    
            protected void beforeExecute(Thread t,Runnable r) {
                System.out.println("Ready to execute:"+ ((ThreadTask)r).getTaskName());
            }
            
            protected void afterExecute(Runnable r,Throwable t) {
                System.out.println("Execution completed:"+((ThreadTask)r).getTaskName());
            }
            
            protected void terminated() {
                System.out.println("Thread pool exit");
            }
        };
          
        for(int i=0;i<10;i++) {
            pool.execute(new ThreadTask("Task"+i));
        }    
        pool.shutdown();
    }
}

public class ThreadTask implements Runnable{    
    private String taskName;
    public String getTaskName() {
        return taskName;
    }
    public void setTaskName(String taskName) {
        this.taskName = taskName;
    }
    public ThreadTask(String name) {
        this.setTaskName(name);
    }
    public void run() {
        //Name of output execution thread
        System.out.println("TaskName"+this.getTaskName()+"---ThreadName:"+Thread.currentThread().getName());
    }
}

Let me see the output

Thread 118352462 created
Thread 1550089733 Creation
Ready to execute: Task0
Ready to execute: Task1
TaskNameTask0---ThreadName:threadPool118352462
Thread 865113938 Created
Execution complete: Task0
TaskNameTask1---ThreadName:threadPool1550089733
Execution complete: Task1
Ready to execute: Task3
TaskNameTask3---ThreadName:threadPool1550089733
Execution complete: Task3
Ready to execute: Task2
Ready to execute: Task4
TaskNameTask4---ThreadName:threadPool1550089733
Execution complete: Task4
Ready to execute: Task5
TaskNameTask5---ThreadName:threadPool1550089733
Execution complete: Task5
Ready to execute: Task6
TaskNameTask6---ThreadName:threadPool1550089733
Execution complete: Task6
Ready to execute: Task8
TaskNameTask8---ThreadName:threadPool1550089733
Execution complete: Task8
Ready to execute: Task9
TaskNameTask9---ThreadName:threadPool1550089733
Ready to execute: Task7
Execution complete: Task9
TaskNameTask2---ThreadName:threadPool118352462
TaskNameTask7---ThreadName:threadPool865113938
Execution complete: Task7
Execution complete: Task2
Thread pool exit

You can see that through the implementation of beforeExecute(), afterExecute(), and terminated(), we monitor the running state of threads in the thread pool and output print information before and after their execution.It is also safer to close the thread pool using the shutdown method, which no longer accepts subsequent added tasks when called by the thread pool.However, at this point, the thread pool does not exit immediately until all the tasks added to the thread pool have been processed.

5. Number of Threads in Thread Pool

Threads do not have a clear indicator of the number of threads eaten. As long as it is not too big or too small for the settings, it is OK to combine the following formula.

            /**
             * Nthreads=CPU Number
             * Ucpu=Target CPU utilization, 0<=Ucpu<=1
             * W/C=Ratio of task wait time to task calculation time
             */
            Nthreads = Ncpu*Ucpu*(1+W/C)

That's how the ThreadPoolExecutor class is used in detail from constructors, denial policies, custom thread creation, etc. so that we can configure and use thread pools to create threads flexibly according to our own needs, including pointing out and culverts if they are inadequate or incorrect.

 

For its three closing methods (shutdown(), shutdownNow(), awaitTermination(), here's a look at these three methods:

shutdown()

Setting the thread pool state to SHUTDOWN does not stop immediately: Tasks running inside the task receiving external submit and tasks waiting in the queue are stopped until the second step is complete

shutdownNow()

Set the thread pool state to STOP.Attempting to stop immediately is not really necessary: like shutdown(), stop receiving externally submitted tasks first, ignore waiting tasks in the queue Attempting to interrupt a running task back to the list of unexecuted tasksIt attempts to terminate a thread by calling the Thread.interrupt() method, but as you know, this method has a limited effect. If there are no applications in a thread such as sleep, wait, Condition, timelock, etc., the interrupt () method cannot be interrupted.

Of the previous thread.Therefore, ShutdownNow() does not mean that the thread pool must be able to exit immediately; it may also have to wait for all the tasks being performed to exit.But most of the time you can quit immediately

awaitTermination

Two parameters, timeout and TimeUnit, are used to set the timeout time and unit.When the wait time exceeds the set time, the ExecutorService is monitored to see if it is closed, returning true if it is closed, or false if it is not.Typically this is used in combination with the shutdown method.

Take an example:

package com.secbro.test.thread;
 
import java.util.concurrent.Callable;
 
/**
 * @author zhuzhisheng
 * @Description
 * @date on 2016/6/1.
 */
public class Task implements Callable{
    @Override
    public Object call() throws Exception {
        System.out.println("Common tasks");
        return null;
    }
}

package com.secbro.test.thread;
 
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
 
/**
 * @author zhuzhisheng
 * @Description
 * @date on 2016/6/1.
 */
public class LongTask implements Callable{
    @Override
    public Object call() throws Exception {
        System.out.println("Long Tasks");
        TimeUnit.SECONDS.sleep(5);
        return null;
    }
}

package com.secbro.test.thread;
 
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
 
/**
 * @author zhuzhisheng
 * @Description
 * @date on 2016/6/1.
 */
public class TestShutDown {
 
    public static void main(String[] args) throws InterruptedException{
        ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
 
        service.submit(new Task());
        service.submit(new Task());
        service.submit(new LongTask());
        service.submit(new Task());
 
 
        service.shutdown();
 
        while (!service.awaitTermination(1, TimeUnit.SECONDS)) {
            System.out.println("Thread pool not closed");
        }
 
        System.out.println("Thread pool closed");
    }
 
}

Output:

Common tasks
Common tasks
Long Tasks
Common tasks
Thread pool not closed
Thread pool not closed
Thread pool not closed
Thread pool not closed
Thread pool closed

Here's the demo I've used:

public int insertMobileSrcBatch(String tableName, Integer dataMonth, Integer dataDate, Date firstDayOfMonth, String province, List<Integer> batchIds, List<RentInfoIDVo> allIds, List<BusinessException> insertException) {
        //Establish thread pool
        ExecutorService executorService = new ThreadPoolExecutor(10, 10,
                120, TimeUnit.MINUTES, new LinkedBlockingDeque<>(Integer.MAX_VALUE),
                new ThreadFactoryBuilder().setNameFormat("TFeeTask-TW-CD-%d").setDaemon(false).build());
        int idsLen = allIds.size();
        int pageSize = 2000;
        ArrayBlockingQueue<Integer> insertedCount = new ArrayBlockingQueue<>(idsLen);
        for (int pos, offset = 0; offset < idsLen; ) {
            pos = offset;
            offset = offset + pageSize;
            if (offset > idsLen) {
                offset = idsLen;
            }
            List<RentInfoIDVo> subIds = allIds.subList(pos, offset);
            XxlJobLogger.log("[TFeeTask]Start processing section{}to{}Data.", (pos + 1), offset);
            executorService.execute(() ->  asyncTowerRentInfoInsertThreadLocalService.queryAndInsertMobileSrc( tableName, dataMonth, dataDate, firstDayOfMonth, province, batchIds, subIds, insertedCount, insertException));
        }
        executorService.shutdown();
        try {
            boolean finish;
            do {
                finish = executorService.awaitTermination(1, TimeUnit.MINUTES);
            } while (!finish);
        } catch (InterruptedException e) {
            XxlJobLogger.log("[TFeeTask]Thread Wait Failed");
        }
        return insertedCount.stream().mapToInt((x) -> null == x ? 0 : x).sum();
    }

Posted by pesoto74 on Thu, 23 Apr 2020 18:10:16 -0700