Java Concurrent Programming (6) Thread Pool [Executor System]

Overview

When processing a large number of tasks, reusing threads can improve program execution efficiency, so thread pools emerged as the times require.

  • It is a mechanism for reusing threads, which can effectively reduce memory resource consumption.
  • Improve response speed . When a task arrives, the task can be executed immediately without waiting for the thread to be created.
  • Thread pools can help us better manage the life cycle and resource usage of threads , and avoid performance problems caused by frequent creation and destruction of threads.

At the same time, the thread pool can also provide some additional functions, such as thread pool size control, thread pool task queue, thread pool rejection policy, etc. The thread pool usually maintains a thread queue, which stores created threads. When a new task needs to be executed, the thread in the thread pool can take out a thread from the queue to execute the task. After the task is completed, the thread Can be put back into the thread queue and wait for the arrival of the next task

core execution process

Thread pool execution process
  • When a new task requires thread execution, the thread pool will first determine whether there are idle core threads, and if so, the task will be assigned to one of the idle core threads for execution. If there are no idle core threads or the number of core threads has not reached the maximum, a new core thread is created to perform the task.
  • If the core thread has reached the maximum value and the work queue is not full, the newly submitted task will be stored in this work queue. If the work queue is full, it will determine whether the number of threads in the thread pool has reached the maximum value. If not, a new non-core thread will be created to perform the task.
  • If the work queue is full and all threads in the thread pool are already working, that is, both core threads and non-core threads are executing tasks, the saturation strategy is used to handle the task. The saturation strategy can determine how to handle tasks that cannot be processed, such as throwing exceptions or blocking task submission.

Thread pool status

  • RUNNING: This state represents the ability to accept new tasks and process tasks (initial state)
  • SHUTDOWN: This state means that new tasks are not accepted, but added tasks are processed (when shutdown() is called, by RUNNING->SHUTDOWN)
  • STOP: This state means that new tasks will not be accepted, added tasks will not be processed, and tasks being processed will be interrupted (when shutdownNow() is called, from RUNNING or SHUTDOWN→STOP)
  • TIDYING: After entering the SHUTDOWN or STOP state, it will enter this state after all tasks have been processed or cleaned up, and the terminated() method will be executed at the same time (this method is a hook function, custom implementation)
  • TERMINATED: end state, after executing the terminated method, it is TIDYING->TERMINATED

Executor framework

Two-level scheduling model 

In the threading model of HotSpotVM, Java threads (java.lang.Thread) are mapped one-to-one to local operating system threads.
When a Java thread starts, it creates a local operating system thread; when the Java thread terminates, this operating system thread also will be recycled. The operating system schedules all threads and assigns them to available CPUs

  • In the upper-layer architecture, Java multi-threaded programs usually decompose the application into several tasks, and the application maps these tasks into a fixed number of threads through the Executor framework.
  • In the lower-layer architecture, the operating system kernel maps these threads to the hardware processor, and the lower-layer scheduling is not controlled by the application program.

Executor structure

The main classes and interfaces included in the Executor framework are shown in the figure

  • Executor is an interface, which is the basis of the Executor framework. It separates the submission of tasks from the execution of tasks.
  • ThreadPoolExecutor is the core implementation class of the thread pool, used to execute submitted tasks
  • ScheduledThreadPoolExecutor is an implementation class that is used to delay the execution of tasks later or to execute tasks regularly . ScheduledThreadPoolExecutor is more flexible and more powerful than Timer
  • The FutureTask class that implements the Future interface represents the result of asynchronous calculation.
  • The implementation classes of Runnable interface and Callable interface can be executed by ThreadPoolExecutor or ScheduledThreadPoolExecutor.

ThreadPoolExecutor

Detailed description of constructor
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;
}

Parameter introduction:

  • corePoolSize: core thread pool size. Generally speaking, if the task is time-consuming, you can configure the number of CPU cores * 2, because this can make full use of the CPU. If the task is small and executes quickly, you can configure the number of CPU cores + 1 or less (because thread context switching is time-consuming) (get CPU Number of cores: Runtime.getRuntime().availableProcessors())
  • maximumPoolSize: Maximum thread pool size. When the number of threads >= corePoolSize and the task queue is full. The thread pool will create new threads to process tasks, the total number of threads ≤ maximumPoolSize
  • keepAliveTime: idle time, threads that exceed the number of core threads will be destroyed after reaching the idle time
  • TimeUnit: time unit
  • BlockingQueue: Queue used to temporarily save tasks (blocking queue)
  • ThreadFactory: A custom thread factory. The default is a new, non-daemon thread and does not contain special configuration information. We can also customize and add our debugging information, such as thread name, error log, etc.
  • RejectedExecutionHandler: saturation strategy. When the number of threads = maximumPoolSize and the task queue is full, the measures that need to be taken for redundant tasks include the following (default AbortPolicy):
    • AbortPolicy: discard the task and throw RejectedExecutionException
    • DiscardPolicy: Discard this task without any exception
    • DiscardOldestPolicy: Discard the oldest. That is to say, if the queue is full, the earliest task to enter the queue will be deleted to make room, and then try to join the queue.
    • CallerRunsPolicy: The main thread will perform the task itself and will not wait for threads in the thread pool to perform it.
    • Customization: Of course, you can also customize the strategy
Commonly used ThreadPoolExecutor types

ThreadPoolExecutor is usually created using factory class Executors , including 3 types of ThreadPoolExecutor:

  • FixedThreadPool: A thread pool that can reuse a fixed number of threads
  • SingleThreadExecutor: thread pool for a single thread (only one worker thread)
  • CachedThreadPool: A thread pool that creates new threads as needed
FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
	return new ThreadPoolExecutor(nThreads, nThreads,
								  0L, TimeUnit.MILLISECONDS,
								  new LinkedBlockingQueue<Runnable>());
}
  • Among them, corePool and maximumPoolSize are both set to the specified parameters.
  • keepAliveTime is set to 0L, which means that excess idle threads will be terminated immediately
  • It is suitable for application scenarios where the number of current threads needs to be limited in order to meet the needs of resource management. It is more suitable for servers with heavy loads.
fixedThreadPool execution process
  • If there are fewer current threads than corePool, create new threads to perform the current task
  • When the number of running threads is equal to corePool, add the task to the LinkedBlockingQueue queue
  • After the thread completes the current task, it will loop and repeatedly obtain tasks from LinkedBlockingQueue for execution.

Since it is a LinkedBlockingQueue unbounded queue (length Integer.MAX_VALUE), the following scenario will occur:

  • The maximumPoolSize and keepAliveTime parameters will be invalid because maximumPoolSize=corePool
  • The task will not be rejected because it is an unbounded queue and the task will not be full.
SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
	return new FinalizableDelegatedExecutorService
		(new ThreadPoolExecutor(1, 1,
								0L, TimeUnit.MILLISECONDS,
								new LinkedBlockingQueue<Runnable>()));
}

corePool and maximumPoolSize are both set to 1, and other effects and operation methods are the same as FixedThreadPool

CachedThreadPool
public static ExecutorService newCachedThreadPool() {
	return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
								  60L, TimeUnit.SECONDS,
								  new SynchronousQueue<Runnable>());
}
  • The number of core threads in corePoolSize is 0, and the maximumPoolSize is Integer.MAX_VALUE ( more than 2.1 billion ), which means that if there are no idle threads, threads will continue to be created for execution. In extreme cases, CPU and memory resources will be exhausted;
  • The idle thread will be terminated after keepAliveTime=60s, so it will not occupy any resources if it remains idle for a long time.
  • SynchronousQueue is a blocking queue with no capacity. Each insertion operation will wait for the corresponding removal operation of another thread.
  • Suitable for small programs that perform many short-term asynchronous tasks, or servers with light loads

ScheduleThreadPoolExecutor

Constructor
public ScheduledThreadPoolExecutor(int corePoolSize,
								   RejectedExecutionHandler handler) {
	super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
		  new DelayedWorkQueue(), handler);
}

ScheduledThreadPoolExecutor makes the following modifications to ThreadPoolExecutor in order to implement periodic tasks:

  • Use DelayedWorkQueue as task queue
  • The methods of obtaining tasks are different. They are all queue take, but with added time judgment.
  • After executing the periodic task, additional processing is added (the task needs to be re-added to the queue)
Commonly used ScheduleThreadPoolExecutor types

ScheduledThreadPoolExecutor is usually created using factory class Executors, 2 types:

  • ScheduledThreadPoolExecutor: Contains several threads ScheduledThreadPoolExecutor
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) 
    • ScheduledThreadPoolExecutor is suitable for application scenarios that require multiple background threads to perform periodic tasks and limit the number of background threads to meet resource management needs.

  • SingleThreadScheduledExecutor: Contains only one thread ScheduledThreadPoolExecutor
    public static ScheduledExecutorService newSingleThreadScheduledExecutor()
    public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)
    • SingleThreadScheduledExecutor is suitable for application scenarios that require a single background thread to execute periodic tasks and ensure that each task is executed sequentially.

FutureTask

The Future interface and the FutureTask class that implements the Future interface are used to represent the results of asynchronous calculations. When we submit the implementation class of Runnable interface or Callable interface to ThreadPoolExecutor or ScheduledThreadPoolExecutor, ThreadPoolExecutor or ScheduledThreadPoolExecutor will return a FutureTask object to us.

Executor Practice

ThreadPoolExecutor

package com.bierce;
import java.util.concurrent.*;
public class TestThreadPoolExecutor {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        //方式一:Runnable方式的分配10个任务提交给线程池
        ThreadPoolDemo threadPoolDemo = new ThreadPoolDemo();
        for (int i = 0; i <= 10; i++) {
            executorService.submit(threadPoolDemo);
        }
        //方式二:Callable方式的分配10个任务提交给线程池
        for (int i = 0; i <= 10; i++) {
            Future<Object> sum = executorService.submit(() -> {
                int sum1 = 0;
                for (int i1 = 1; i1 <= 100; i1++) {
                    sum1 += i1;
                }
                return sum1;
            });
            System.out.println(Thread.currentThread().getName() + ":" + sum.get()); //main:5050
        }
        //关闭线程池
        executorService.shutdown();
    }
}
class ThreadPoolDemo implements Runnable{
    private int i = 0;
    @Override
    public void run() {
        while (i<10){
            System.out.println(Thread.currentThread().getName() + ":" + i++);
        }
    }
}

ScheduleThreadPoolExecutor

package com.bierce;
import java.util.Random;
import java.util.concurrent.*;
public class TestScheduleThreadPool {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
        for (int i = 1; i <=5 ; i++) {
            //调用schedule方法执行任务
            Future<Integer> random = scheduledExecutorService.schedule(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    int random = new Random().nextInt(100);
                    System.out.println(Thread.currentThread().getName() + ":" + random);
                    return random;
                }
            },1,TimeUnit.SECONDS); //每隔一秒执行一个任务
            System.out.println(random.get());
        }
        scheduledExecutorService.shutdown(); //关闭线程池
    }
}

Guess you like

Origin blog.csdn.net/qq_34020761/article/details/132260071