Java thread pool Basics

Posted by intenz on Tue, 21 Sep 2021 12:56:55 +0200

The Executor framework is used to decouple task submission and execution. Task submission is handed over to Runnable or Callable, while the Executor framework is used to process tasks. The core member variable in the Executor framework is threadexector, which is the core implementation class of thread pool.

ThreadPoolExecutor constructor

//Five parameter constructor
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue)

//Six parameter constructor - 1
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory
                         )
//Six parameter constructor - 2
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler)
//Constructor with seven parameters
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)                       
  • corePoolSize

Number of core threads. By default, the thread pool is empty. Threads are created only when a task is submitted. If the number of currently running threads is less than corePoolSize, create a new thread to process the task; If it is equal to or more than coorPoolSize, it will not be created. If the prestartAllcoreThread method of the thread pool is called, the thread pool will create and start all core threads in advance to wait for tasks.

  • maximumPoolSize

The maximum number of threads allowed to be created by the thread pool. If the task queue is full and the number of threads is less than maximumPoolSize, the thread pool will still create new threads to process tasks.

  • keepAliveTime

The idle timeout of the non core thread. If it exceeds this time, it will be recycled. If the allowCoreThreadTimeOut property is set to true, keepAliveTime will also be applied to the core thread.

  • TimeUnit

The time unit of the keepAliveTime parameter. The optional units are day, hour, minute, second and millisecond.

  • workQueue

Task queue. If the current number of threads is greater than corePoolSize, the task is added to this task queue. The task queue is of BlockingQueue type, that is, blocking queue.

  • ThreadFactory

Thread factory. You can use a thread factory to set a name for each thread that creates a process. Generally, this parameter does not need to be set.

  • RejectExecutionHandler

Saturation strategy. This is the coping strategy taken when the task queue and thread pool are full. The default is AbordPolicy, which means that new tasks cannot be processed, and RejectExecutionException is thrown.

  1. CallerRunsPolicy: use the thread of the caller to process the task. This strategy provides simple feedback control and can slow down the submission of new tasks.
  2. DiscardPolicy: delete the task that cannot be executed.
  3. DiscardOldestPolicy: discards the latest task in the queue and executes the current task.

Thread pool lifecycle

  • RUNNING

Indicates that the thread pool is RUNNING, can accept newly submitted tasks, and can process added tasks. The RUNNING state is the initialization state of the thread pool. Once the thread pool is created, it is in the RUNNING state.

  • SHUTDOWN

The thread is closed and does not accept new tasks, but can process added tasks. The RUNNING thread pool will enter the shutdown state after calling shutdown.

  • STOP

The thread pool is in a stopped state, does not receive tasks, does not process added tasks, and will interrupt the threads executing tasks. The RUNNING thread pool will enter the STOP state after calling shutdown now.

  • TIDYING

When all tasks have been terminated and the number of tasks is 0, the thread pool will enter TIDYING. When the thread pool is in SHUTDOWN state, the tasks in the blocking queue are completed, and there are no executing tasks in the thread pool, the state will change from SHUTDOWN to TIDYING. When a thread is in the STOP state and there is no task being executed in the thread pool, it will change from STOP to TIDYING.

  • TERMINATED

Thread termination status. The thread in TIDYING state enters the TERMINATED state after executing terminated().

Processing flow and execution principle of thread pool

Processing flow

  1. After submitting the task, the thread pool first judges whether the number of threads reaches the number of core threads. If the number of core threads is not reached, a core thread is created to process the task; Otherwise, proceed to the next step.
  2. The thread pool then determines whether the task queue is full. If it is not full, add the task to the task queue; Otherwise, proceed to the next step.
  3. Then, because the task queue is full, the thread pool determines whether the number of threads has reached the maximum number of threads. If not, create a non core thread to process the task; Otherwise, the saturation policy will be executed, and the RejectedExecutionException exception will be thrown by default.

Execution principle

  1. If the number of threads in the thread pool does not reach the number of core threads, a core thread is created to process the task.
  2. If the number of threads is greater than or equal to the number of core threads, the task will be added to the task queue, and the idle threads in the thread pool will continuously take out tasks from the task queue for processing.
  3. If the task queue is full and the number of threads does not reach the maximum number of threads, a non core thread is created to process the task.
  4. If the number of threads exceeds the maximum number of threads, the saturation policy is executed.

Type of thread pool

Different types of threadpoolexecutors can be created by directly or indirectly configuring the parameters of ThreadPoolExecutor. Among them, four kinds of thread pools are commonly used: FixThreadPool, cachedthreadpool, singlethreadexecution and scheduledthreadpool.

FixThreadPool

It is created through the newFixedThreadPool method of Executors. It is a thread pool with a fixed number of threads. When threads are idle, they will not be recycled unless the thread pool is closed. When all threads are active, the new task will be in the waiting state until a thread is idle. Because FixThreadPool has only core threads and these core threads will not be recycled, it means that it can respond to external requests more quickly.

example
public class ThreadPoolDemo {

	public static void main(String[] args) {
		MRunable mRunable=new MRunable();
		ExecutorService fixThreadPool=Executors.newFixedThreadPool(4);
		fixThreadPool.execute(mRunable);
		
	}
	
	static class MRunable implements Runnable {
		public void run() {
			System.out.println("Start task");
			try {
				TimeUnit.MILLISECONDS.sleep(2000);
				System.out.println("Pause execution for 2 seconds");
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			System.out.println("End of task execution");
		}
	}

}
Source code analysis
// Source code
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

The corePoolSize and maximumPoolSize of FixThreadPool are set to the parameter nThreads specified for creating FixThreadPool, which means that FixedThredPool has only core threads and a fixed number of non core threads. Setting keepAliveTime to 0L means that redundant threads will be terminated immediately. The task queue adopts the unbounded blocking queue LinkBlockingQueue.

FixThreadPool execution diagram


As can be seen from the figure, when the execute method is executed, if the currently running thread does not reach the corePoolSize (number of core threads), the core thread will be created to process the task. If the number of core threads is reached, the task will be added to LinkBlockingQueue. FixThreadPool is a thread pool with a fixed number of core threads, and these core threads will not be recycled. When the number of threads exceeds the corePoolSize, the task is stored in the task queue; When there are idle threads in the thread pool, the task is fetched from the task queue for execution.

CachedThradPool

Created by the Executors' newCachedThradPool method. It is an indefinite number of thread pools. It has only non core threads and its maximum number of threads is Integer.MAX_VALUE. When all threads in the thread pool are active, the thread pool will create new threads to process new tasks, otherwise idle threads will be used to process new tasks.

example
public class ThreadPoolDemo {

	public static void main(String[] args) {
		MRunable mRunable=new MRunable();
		ExecutorService cacheThreadPool=Executors.newCachedThreadPool();
		cacheThreadPool.execute(mRunable);
	}
	
	static class MRunable implements Runnable {
		public void run() {
			System.out.println("Start task");
			try {
				TimeUnit.MILLISECONDS.sleep(2000);
				System.out.println("Pause execution for 2 seconds");
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			System.out.println("End of task execution");
		}
	}

}
Source code analysis
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

CachedThradPool has a corePoolSize of 0 and a maximumpoolsize of Integer.MAX_VALUE, which means that CachedThradPool has no core threads and non core threads are unbounded. If keepAliveTime is set to 60L, the maximum time for idle threads to wait for new tasks is 60s, and idle threads exceeding 60s will be recycled. The blocking queue SynchronousQueue is a blocking queue that does not store elements. Each insertion operation must wait for the removal operation of another thread. Similarly, any removal operation waits for the insertion operation of another thread.

CachedThradPool execution diagram

When the execute method is executed, the offer method of SynchronousQueue will be executed to submit the task, and whether there are idle threads in the thread pool will be queried to execute the poll method of SynchronousQueue to remove the task. If yes, the pairing is successful, and the task is handed over to the idle thread for processing; If not, pairing fails and a new thread is created to process the task. When the thread in the thread pool is idle, it will execute the poll method of SynchronousQueue and wait for SynchronousQueue to interrupt the submission task. If no new task is submitted to the SynchronousQueue for more than 60s, the idle thread will terminate. Because the maximumPoolSize is unbounded, new threads will be created continuously if the submitted tasks are faster than the threads in the thread pool. In addition, each time a task is submitted, a thread will immediately process it. Therefore, CachedThradPool is more suitable for a large number of tasks that need to be processed immediately and take less time.

SingleThreadExecutor

Created through the newsinglethreadexector method of Executors. This kind of thread pool has only one core thread, which ensures that all tasks are executed sequentially in the same thread. The significance of singlethreadexecution is to unify all external tasks into one thread, so that there is no need to deal with thread synchronization between these tasks.

example
public class ThreadPoolDemo {

	public static void main(String[] args) {
		MRunable mRunable=new MRunable();
		ExecutorService singleThreadPool=Executors.newSingleThreadExecutor();
		singleThreadPool.execute(mRunable);
		
		
	}
	
	static class MRunable implements Runnable {
		public void run() {
			System.out.println("Start task");
			try {
				TimeUnit.MILLISECONDS.sleep(2000);
				System.out.println("Pause execution for 2 seconds");
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("End of task execution");
		}
	}

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

Both corePoolSIze and maximumPoolSize are 1, which means that singlethreadexecution has only one core thread.

SingleThreadExecutor execution diagram

When executing the execute method, if the number of currently running threads does not reach the number of core threads, that is, there are no currently running threads, a new thread is created to process the task. If there are currently running threads, add the task to the blocking queue LinkedBlockingQueue. Therefore, the SingleThread executor can ensure that all tasks are executed one by one in a thread.

ScheduledThredPool

Created through the Executors' newScheduledThredPool method. Its core threads are fixed, while non core threads are unlimited, and will be recycled immediately when non core threads are idle. Thread pools such as ScheduledThredPool are mainly used to execute scheduled tasks and repetitive tasks with cycles.

example
public class ThreadPoolDemo {

	public static void main(String[] args) {
		MRunable mRunable=new MRunable();
		
		ScheduledExecutorService scheduleThreadPool=Executors.newScheduledThreadPool(4);
		// Timed task
		scheduleThreadPool.scheduleAtFixedRate(mRunable, 0, 3000, TimeUnit.MILLISECONDS);
		
		
	}
	
	static class MRunable implements Runnable {
		public void run() {
			System.out.println("Start task");
			try {
				TimeUnit.MILLISECONDS.sleep(2000);
				System.out.println("Pause execution for 2 seconds");
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			System.out.println("End of task execution");
		}
	}

}
Source code analysis
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue());
}

The construction method of ScheduledThreadPoolExecutor finally calls the construction method of ThreadPoolExecutor. corePoolSize is a fixed value passed in, and the value of maximumPolSize is Integer.MAX_VALUE.

ScheduledThredPool execution diagram

When the ScheduleAtFixedRate or ScheduleWithFixedDelay methods of the ScheduledThreadPoolExecutor are executed, an interface implementing RunnableScheduledFuture is added to the DelayedWorkQueue. ScheduledFutureTask (the wrapper class of the task) and checks whether the running thread reaches corePoolSize. If not, create a new thread and start it. Instead of executing the task immediately, go to the DelayedWorkQueue to get the ScheduledFutureTask and then execute the task. If the running thread reaches the corePoolSize, the task is added to the DelayedWorkQueue. DelayedWorkQueue will sort the tasks, and the tasks to be executed first will be placed in front of the queue. After the task is executed, the time variable in ScheduledFutureTask will be changed to the next execution time and put back into DelayedWorkQueue.

Topics: Java thread pool