Java concurrent programming Series 25: Thread Pool
Thread Pool Introduction
In the previous articles about the Executor framework, I had a preliminary understanding of the thread pool. In fact, the design idea of the thread pool is very common in Java, such as the constant pool in JVM, and the database connection pool used by Web development. These pools are essentially Java object pools, because they all store Java objects. Return to the thread pool. Almost all programs that require asynchronous or concurrent tasks can use the thread pool. The main benefits of using a thread pool include the following:
1. improve resource utilization. Because the threads in the thread pool can be reused, it achieves the purpose of recycling.
2. Improve the response speed. Because thread creation also requires overhead, if a request arrives, you can directly use the created thread object to naturally increase the response speed.
Third, it facilitates unified management of threads. Threads are scarce resources. If you create a thread without limit, it will not only consume a large amount of resources, but also greatly reduce the system stability. The thread pool can be used to uniformly allocate and monitor threads.
Implementation principle of Thread Pool
In fact, the thread pool can be summarized in one sentence: by storing the threads created in advance, you can directly use them when necessary. However, to improve the performance of the thread pool, the actual thread pool is much more complex than this simplified version. In the preceding thread pool, only Runnable and Callable tasks are received, or they are called work units. Then we will analyze what happens when a unit of work is submitted to the thread pool.
The thread pool first determines
Core Thread PoolWhether all threads are executing tasks. If not, a new worker thread is created to execute the task. If all tasks are being executed, that is, if there are no Idle threads, enter the next process thread pool to continue judgment.
Work queueWhether it is full. If the work queue is not full, add the newly submitted tasks to the work queue. If the work queue is full, enter the thread pool of the next process to determine whether all threads in the thread pool are in the working state. If not, create a new thread to execute the submitted task. If yes, execute
Saturation strategy.
The following is the execution process of the thread pool:
The core class of the thread pool implemented in Java is ThreadPoolExecutor. the execution process of the execute method of this class is the above process. Note the above three bold words: Core Thread Pool, working queue, and saturation policy. Refined to ThreadPoolExecutor to execute the execute method. The process is supplemented as follows: Core Thread Pool corresponds to corePoolSize variable value. If the running thread is smaller than corePoolSize, a new thread is created to execute the task (In this process, you need to obtain the global lock.); If the running thread is greater than corePoolSize, the task is added to BlockingQueue (corresponding to the working Queue); if it cannot be added, a new thread is created to execute the task. In this step, if the number of currently running threads is greater than maximumPoolSize after a new thread is created, the task is denied and RejectedExecutionHandler is called. rejectedExcution () method.
ThreadPoolExecutor to prevent the execution of newly submitted tasks from obtaining the global lock, ThreadPoolExecutor executes a push process after creation. The so-called push means that the number of currently running threads is greater than or equal to corePoolSize. In this way, all newly submitted tasks will be directly added to BlockingQueue. This process does not need to obtain global locks. Naturally, it can improve the performance of the thread pool. To find out the process of executing the execute Method on ThreadPoolExecutor, we can find out its source code:
Public void execute (Runnable command) {if (command = null) throw new NullPointerException (); int c = ctl. get (); // if the number of currently running threads is smaller than corePoolSize, a new thread is created. // if (workerCountOf (c) <corePoolSize) is executed for the current task) {if (addWorker (command, true) return; c = ctl. get () ;}// if the number of currently running threads is greater than or equal to corePoolSize or the thread creation fails // put the current task into the work queue if (isRunning (c) & workQueue. offer (command) {int recheck = ctl. get (); // determine whether a thread has been added before to execute this task (because It may have been before) // The created thread has been killed) or whether the thread pool has been closed. If the two answers are yes, you can choose to reject the execution of the task if (! IsRunning (recheck) & remove (command) reject (command); else if (workerCountOf (recheck) = 0) addWorker (null, false );} // If the thread pool task cannot be added to the work queue (indicating that the work queue is full) // create a thread to execute the task. If the number of threads currently running after the new creation is greater than // maximumPoolSize, the task else if (! AddWorker (command, false) reject (command );}
If the thread pool can create a thread to execute tasks, the addWorker method is called to encapsulate the threads created in the thread pool as Worker, after the Worker completes the task, it cyclically obtains the tasks in the queue for execution. Let's look at the source code of the addWorker method:
Private boolean addWorker (Runnable firstTask, boolean core) {// omitting some code boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try {// here, the submitted task is encapsulated into a Worker w = new Worker (firstTask); final Thread t = w. thread; if (t! = Null) {// use the lock method to add the working thread final ReentrantLock mainLock = this. mainLock; mainLock. lock (); try {// re-check the running status of the thread pool during lock acquisition: if // The thread pool is closed or the task is empty, an exception int rs = runStateOf (ctl. get (); if (rs <SHUTDOWN | (rs = SHUTDOWN & firstTask = null) {if (t. isAlive () throw new IllegalThreadStateException (); // Add the Worker array workers. add (w); int s = workers. size (); if (s> largestPoolSize) largestPoolSize = s; workerAd Ded = true ;}} finally {mainLock. unlock ();} if (workerAdded) {// if the addition is successful, start the thread to execute the task t. start (); workerStarted = true ;}} finally {if (! WorkerStarted) addWorkerFailed (w);} return workerStarted ;}
Then let's take a look at what will happen after the execution of t. start (). Because the Worker itself implements Runnable, the Worker run method will be called after start. The source code is as follows:
public void run() { runWorker(this); } final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { while (task != null || (task = getTask()) != null) { w.lock(); try { beforeExecute(wt, task); Throwable thrown = null; task.run(); afterExecute(task, thrown); } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); } }
The above source code actually does one thing: After the created thread executes the submitted task, it will repeatedly obtain the task from BlockingQueue for execution.
Use thread pool
We have analyzed the execution process of the thread pool and analyzed the source code. Next we will create a thread pool and familiarize ourselves with the use of the thread pool. Several input parameters are required to create a thread pool:
CorePoolSize: the Basic Size of the thread pool. When a task is submitted to the thread pool, the thread pool creates a thread execution task, but a new thread is created even if the thread pool has Idle threads, it is not created until the number of executed tasks is greater than or equal to corePoolSize. RunnableTaskQueue: the blocking queue used to save tasks waiting for execution. MaximumPoolSize: Maximum number of thread pools. If the working queue of the thread pool is full and the number of created threads is smaller than the maximum number of threads, the thread pool creates a new thread to execute tasks. ThreadFactory: used to set the factory for creating a thread. RejectedExecutionHandler: Saturation policy. When the queue and thread pool are full, it indicates that the thread is saturated. Therefore, you must adopt a policy to process submitted tasks. The default policy is to reject execution, that is, to throw an exception.
You can submit a task in two ways: execute () and submit (). The difference is that the tasks submitted by the latter return values. The returned value can be obtained through the get () method of the Future object. After execution, you need to call the thread pool to close the thread pool. There are two main methods: shutdown () and shutdownNow (). The principle is to traverse the worker threads in the thread pool, and then call the thread's interrupt () method one by one to interrupt the thread. The difference is that the shutdownNow () method first sets the thread pool status to STOP, then tries to terminate the thread being executed and the thread waiting for execution, and returns the list of tasks waiting for execution; the shutdown () method only sets the state of the thread pool to SHUTDOWN, and then interrupts all threads that are not being executed. Generally,If the thread pool is closed after the task is executed, the shutdown () method is called. If you do not have to wait until the task is executed, the shutdownNow () method is executed.
An example of a simple thread pool is as follows (wheel creation is recommended ):
/*** Created by rhwayfun on 16-4-7. * // thread pool interface public interface ThreadPool
{// Execution task void execute (Job job); // close the thread pool void shutdown ();} // demonstrate using the thread pool package com. rhwayfun. patchwork. concurrency. r0407; import java. util. arrayList; import java. util. list; import java. util. concurrent. arrayBlockingQueue; import java. util. concurrent. blockingQueue; import java. util. concurrent. atomic. atomicLong; public class DemoThreadPool
Implements ThreadPool
{// Blocking queue private BlockingQueue
WorkQueue = null; // Save the private List of worker threads in the thread pool
DemoThreadList = Collections. synchronizedList (new ArrayList
(); // The thread pool status is private boolean isShutdown = false; // The default size of the thread pool is private static final int DEFAULT_WORKER_NUM = 5; // maximum size of the thread pool private static final int MAX_WORKER_NUM = 10; // minimum size of the thread pool private static final int MIN_WORKER_NUM = 1; // Number of worker threads private int workNum; // Thread No. private AtomicLong threadNum = new AtomicLong (); public DemoThreadPool (int num) {workNum = num> MAX_WORKER_NUM? MAX_WORKER_NUM: num <MIN_WORKER_NUM? MIN_WORKER_NUM: num; init (workNum);} public DemoThreadPool () {init (DEFAULT_WORKER_NUM );} /*** initialize the thread pool * @ param workNum */private void init (int workNum) {// initialize the workQueue of the work queue = new ArrayBlockingQueue <> (DEFAULT_WORKER_NUM ); // Add a specified number of worker threads to the list for (int I = 0; I <workNum; I ++) {demoThreadList. add (new DemoThread (workQueue);} // start a specified number of worker threads for (DemoThread thread: demoThreadList) {Thread worke R = new Thread (thread, "ThreadPool-Worker-" + threadNum. incrementAndGet (); System. out. println ("ThreadPool-Worker-" + threadNum. get () + "add to workQueue! "); Worker. start () ;}}/*** execute a task * @ param job */@ Override public void execute (Runnable job) {if (isShutdown) throw new IllegalStateException ("ThreadPool is shutdown! "); If (demoThreadList! = Null) {try {// Add a task to the workQueue. put (job); System. out. println ("ThreadPool es a task! ");} Catch (InterruptedException e) {e. printStackTrace () ;}}/ *** close thread pool */@ Override public void shutdown () {isShutdown = true; for (DemoThread t: demoThreadList) {t. stopToSelf () ;}}// the worker thread package com. rhwayfun. patchwork. concurrency. r0407; import java. util. concurrent. blockingQueue; public class DemoThread implements Runnable {private BlockingQueue
WorkQueue; private volatile boolean shutdown = false; public DemoThread (BlockingQueue
WorkQueue) {this. workQueue = workQueue;} @ Override public void run () {while (! Shutdown) {try {Runnable job = workQueue. take (); job. run ();} catch (InterruptedException e) {e. printStackTrace () ;}} public void stopToSelf () {shutdown = true; // call the interrupt method to get the new Thread (this) returned from the wait method for the Thread waiting for the queue to leave ). interrupt () ;}}// Test Program package com. rhwayfun. patchwork. concurrency. r0407; import java. text. simpleDateFormat; import java. util. date; import java. util. concurrent. timeUnit; public class DemoThreadPoolTest {public static void main (String [] args) throws InterruptedException {// final SimpleDateFormat format = new SimpleDateFormat ("HH: mm: ss "); // create the thread pool DemoThreadPool
ThreadPool = new DemoThreadPool <> (); // Add 15 tasks for (int I = 0; I <15; I ++) {Runnable task = new Runnable () {@ Override public void run () {System. out. println (Thread. currentThread (). getName () + "get result" + format. format (new Date () ;}}; threadPool.exe cute (task) ;} threadPool. shutdown (); TimeUnit. SECONDS. sleep (10); System. out. println ("work done! Time is "+ format. format (new Date ()));}}
The execution result of the above program is:
ThreadPool-Worker-1 add to workQueue!
ThreadPool-Worker-2 add to workQueue!
ThreadPool-Worker-3 add to workQueue!
ThreadPool-Worker-4 add to workQueue!
ThreadPool-Worker-5 add to workQueue!
ThreadPool has es a task!
ThreadPool has es a task!
ThreadPool has es a task!
ThreadPool has es a task!
ThreadPool has es a task!
ThreadPool-Worker-1 get result 19:36:48
ThreadPool-Worker-1 get result 19:36:48
ThreadPool-Worker-1 get result 19:36:48
ThreadPool-Worker-1 get result 19:36:48
ThreadPool-Worker-1 get result 19:36:48
ThreadPool has es a task!
ThreadPool has es a task!
ThreadPool has es a task!
ThreadPool has es a task!
ThreadPool has es a task!
ThreadPool-Worker-1 get result 19:36:48
ThreadPool-Worker-1 get result 19:36:48
ThreadPool-Worker-1 get result 19:36:48
ThreadPool-Worker-1 get result 19:36:48
ThreadPool-Worker-1 get result 19:36:48
ThreadPool has es a task!
ThreadPool has es a task!
ThreadPool has es a task!
ThreadPool has es a task!
ThreadPool has es a task!
ThreadPool-Worker-1 get result 19:36:48
ThreadPool-Worker-2 get result 19:36:48
ThreadPool-Worker-3 get result 19:36:48
ThreadPool-Worker-4 get result 19:36:48
ThreadPool-Worker-5 get result 19:36:48
Work done! The Time is 19:37:03
We can see from the implementation of the thread pool that when the client calls execute (Runnable task), the task will be continuously added to the work queue BlockingQueue, the DemoThread of each worker thread keeps pulling tasks from the work queue for execution. When the Task Force column is empty, the worker thread enters the waiting state. After a job is executed, the thread calls the shutdown () method to close the thread pool. Note that the stopToSelf method of the worker thread is called to stop the job execution from the work queue, besides setting the shutdown variable to false, it also calls the interrupt method of the worker thread to interrupt the thread, the purpose of this operation is to interrupt the thread of the waiting worker because the task needs to be retrieved, so that it can return from the wait method and then stop the execution.
As you can see, the essence of the thread pool is to use a thread-safe working queue connection line and client thread. After the client thread puts the task into the work queue, it will return directly, the worker thread constantly extracts and executes tasks from the task column. When the Task Force column is empty, all worker threads are waiting on the Task Force column. When a client submits a new task, any worker thread is notified. As the number of new jobs increases, more worker threads will be awakened.