On the underlying source code of ThreadPoolExecutor thread pool



1, Thread pool Basics

The specific use details of thread pool are beyond the scope of this article. If you want to know the usage, please Baidu. Here is only a small Demo of thread pool

The function of the thread pool is to use the thread pool to execute 100000 threads. The thread task implements the callable interface. Each thread prints the corresponding logic and then returns (the thread pool does not receive the returned results here)

public class TestThreadPool {
	public static void main(String[] args) throws ExecutionException, InterruptedException {
		CopyOnWriteArrayList<Future> retList = new CopyOnWriteArrayList<>();
		List<Task> taskList = new ArrayList<>();
		for (int i = 0; i < 100000; i++) {
			taskList.add(new Task(i));
		}
		ExecutorService threadPool = new ThreadPoolExecutor(
            	1,//Number of threads in the core
				3,//Maximum number of threads
				10,//The maximum number of threads to close after waiting for a certain event
				TimeUnit.MILLISECONDS,//Unit of waiting time
				new LinkedBlockingQueue<>(10),//Create a queue
				Executors.defaultThreadFactory(),//Create a thread factory for threads
				new ThreadPoolExecutor.CallerRunsPolicy());// Reject policy

		for (int i = 0; i < taskList.size(); i++) {
			threadPool.submit(taskList.get(i));
		}
	}
}

class Task implements Callable<Integer> {
	private Integer num;

	public Task(Integer num) {
		this.num = num;
	}

	@Override
	public Integer call() throws Exception {
		System.out.println("---uuu--" + num);
		return num;
	}
}



Three methods

//1. Create a thread pool with only one thread
ExecutorService threadPool = Executors.newSingleThreadExecutor();

//2. Create a scalable thread pool
ExecutorService threadPool = Executors.newCachedThreadPool();

//3. Create a specified maximum number of thread pools
ExecutorService threadPool = Executors.newFixedThreadPool(3);



Seven parameters

ExecutorService threadPool = new ThreadPoolExecutor(
            	1,//Number of threads in the core
				3,//Maximum number of threads
				10,//The maximum number of threads to close after waiting for a certain event
				TimeUnit.MILLISECONDS,//Unit of waiting time
				new LinkedBlockingQueue<>(10),//Create a queue to store tasks
				Executors.defaultThreadFactory(),//Create a thread factory for threads
				new ThreadPoolExecutor.CallerRunsPolicy());// Reject policy



Four rejection strategies

//For extra threads, throw an exception directly
new ThreadPoolExecutor.AbortPolicy()
    
//Whoever starts the thread will return the thread to whoever executes it. For example, if the main thread is started, it will be returned to the main thread for execution    
new ThreadPoolExecutor.CallerRunsPolicy()
    
//If the number of queue threads is full, they will be discarded directly without throwing an exception
new ThreadPoolExecutor.DiscardPolicy()
    
//After the queue is full, try to compete with the earliest thread without throwing an exception
new ThreadPoolExecutor.DiscardOldestPolicy





2, Simple analysis of execute process

1. When the execute method is called, submit sets a RunnableFuture layer on the execute method. (the workQueue in the following code is the queue passed in when we initialize the thread pool)



2. addWorker method. This method mainly focuses on the incoming parameter firskTask, that is, the thread task we passed in, that is, the parameter of the execute method



3. It encapsulates our incoming thread as a worker object. Assign the thread task firstTask to the firstTask property and the worker object to the thread property. (the corresponding getThreadFactory method is to call the thread factory when we initialize the thread pool to create it)



4. Back to the code in step 2, in addition to adding our thread task to the workers collection, we also called the start method of the t variable. The t variable is the thread variable in step 3, even if our worker object



5. Call the start method of the worker object. Since the worker implements the Runnable interface, the start method corresponds to the run method calling the Runnable interface



6, then call the corresponding runWorker method. In this method, get the firstTask attribute of the worker object, that is, the parameter of our execute method, that is, our thread task. Then call the run method of the thread task.



After a brief analysis of this process, we can draw the following conclusions:

  1. When we use the thread pool to execute thread tasks, we call the run method when executing threads, instead of calling the start method to start multithreading
  2. The thread pool encapsulates the incoming thread task into a worker object. In this process, there is a start method that calls the encapsulated worker object





3, Thread exception, follow-up process source code

1. Take the execute method as the entry, and then directly jump to point 6 in the second section above to enter the runWorker method. If an exception occurs in the run method, it will enter the following processWorkerExit method. The parameter w passed in is the worker object



2. In this method, first remove the worker object from the workers queue (as mentioned earlier, all tasks are stored in the workers queue), and then add a task with null firstTask. Here, addWorker method will be called again, but since firstTask is null, there is no corresponding thread task to execute



Summary: that is, if a thread pool is used to execute multiple thread tasks, an exception occurs in one thread. Then the thread pool will catch the thread with exception and remove the thread from the thread task queue. Add another thread task whose task is null, and then execute the null task. It can also be understood in disguise that the thread task we want to execute is discarded.





4, Source code of rejection policy

I believe you can simply remember the role of each rejection strategy after reading the source code

This reject method will be called when using the reject policy, and the corresponding handler will pass in the specific implementation when initializing the thread pool



AbortPolicy: throw an exception directly

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    throw new RejectedExecutionException("Task " + r.toString() +
                                         " rejected from " +
                                         e.toString());
}



CallerRunsPolicy: executed by the thread calling it

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        r.run();
    }
}



DiscardOldestPolicy: eject other thread tasks in the queue (poll)

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        e.getQueue().poll();
        e.execute(r);
    }
}



DiscardPolicy: no operation is performed

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}



Custom reject policy

To customize the rejection policy, you only need to implement the RejectedExecutionHandler interface and rewrite the rejectedExecution method. We can write tasks to mq like middleware or persist them to the database. Write another thread to continuously monitor the queue of the thread pool. If the tasks in the queue drop to a certain threshold, we can read the previous tasks and store them in the queue again.

Tags: Java thread pool threadpool

Posted on Sun, 05 Dec 2021 14:21:40 -0500 by Joe_Dean