The benefits of using thread pools are:

1. Reduce resource consumption

You can reuse created threads to reduce the cost of thread creation and destruction.

2, improve the response speed

When a task arrives, it can be executed immediately without waiting for the thread to be created.

3. Improve thread manageability

Threads are scarce resources. If created without limit, they consume system resources and degrade system stability. Thread pools can be used for uniform allocation, tuning, and monitoring

Executors

Executors create a thread pool

NewFiexedThreadPool (int Threads) : creates a thread pool with a fixed number of Threads.

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads, 0L,
        TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
    }
Copy the code

NewCachedThreadPool () : Create a cacheable thread pool and call execute to reuse previously constructed threads (if available). If no thread is available, a new thread is created and added to the pool. Terminates and removes threads from the cache that have not been used for 60 seconds.


    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS,
        new SynchronousQueue());
    }
Copy the code

NewSingleThreadExecutor () creates a single threaded Executor.


    public static ExecutorService newSingleThreadExecutor() {
        return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, 
        new LinkedBlockingQueue()));
    }
Copy the code

NewScheduledThreadPool (int corePoolSize) creates a thread pool that supports timed and periodic task execution and can be used to replace the Timer class in most cases.


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

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, 2147483647, 10L, TimeUnit.MILLISECONDS, 
        new ScheduledThreadPoolExecutor.DelayedWorkQueue());
    }
Copy the code

Why not use Executors to create a thread pool? Thread pools created in these ways may cause OOM(OutOfMemory).

1) newFiexedThreadPool and newSingleThreadExecutor use an unbounded LinkedBlockingQueue (because no capacity is set) with a maximum length of integer.max_value, Adding tasks to the queue will cause OOM.

Integer.MAX_VALUE specifies the maximum number of threads allowed to create a thread pool in newCachedThreadPool.

It is recommended to create your own thread pool directly using the constructor of ThreadPoolExecutor. At the same time as the BlockQueue is created, you can specify the capacity.

ThreadPoolExecutor

Constructor argument

new ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
Copy the code
  • CorePoolSize Minimum number of threads maintained by the thread pool (core: core)

  • MaximumPoolSize: The maximum number of threads maintained by the thread pool

  • KeepAliveTime: thread pool maintenance thread is allowed to free time – unit: thread pool maintenance thread is allowed to free time units (optional parameters for Java. Util. Concurrent. Several static attributes in TimeUnit: NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS.)

  • WorkQueue: buffer queue used by the thread pool (commonly used are: Java. Util. Concurrent. ArrayBlockingQueue)

  • Handler: Processing policy of the thread pool for rejected tasks.

    • ThreadPoolExecutor. AbortPolicy () : throw Java. Util. Concurrent. RejectedExecutionException anomalies.
    • ThreadPoolExecutor. CallerRunsPolicy () : the task of adding the current retry, calls the execute () thread automatic repeat calls the execute () method.
    • ThreadPoolExecutor. DiscardOldestPolicy () : the old abandoned the queue tasks (and will perform tasks), and execute this task again.
    • ThreadPoolExecutor. DiscardPolicy () : abandon the current task.

The working process of the

The execute(Runnable) method adds a task to the thread pool, which is an object of type Runnable. The task is executed by the Run () method of a Runnable object.

When a task is added to a thread pool using the execute(Runnable) method: If the number of threads in the thread pool is smaller than corePoolSize, a new thread is created to handle the added task even though all the threads in the thread pool are idle.

If the number of threads in the thread pool is equal to corePoolSize, but the buffer queue workQueue is not full, then the task is put into the buffer queue.

If the number of threads in the pool is greater than corePoolSize, the buffer workQueue is full, and the number of threads in the pool is less than maximumPoolSize, a new thread is created to handle the added task.

If the number of threads in the pool is greater than corePoolSize, the buffer queue workQueue is full, and the number of threads in the pool is equal to maximumPoolSize, the task is processed using the policy specified by the handler.

The priority of the rejected task is corePoolSize, workQueue, and maximumPoolSize. If all three are full, the rejected task is processed by handler. When the number of threads in the thread pool is greater than corePoolSize, if a thread is idle for longer than keepAliveTime, the thread is terminated. In this way, the thread pool can dynamically adjust the number of threads in the pool.

The difference between the execute() method and the submit() method

Both methods are used to perform tasks. Before we talk about the difference between the execute() and submit() methods, let’s look at the difference between implementing the Runnable and Callable interfaces.

  • The Runnable interface does not return results, but the Callable interface does.

  • The Call () method of the Callable interface allows exceptions to be thrown; Exceptions to the Runnable interface’s run() method can only be digested internally, not continued

The execute() method has the following differences from the submit() method:

1. The accepted parameters are different

The execute() method accepts only Runnable arguments, while the submit() method accepts both Runnable and Callable arguments

The execute() method finally executes the Runnable run() method, and the submit() method finally executes the Callable call() method. Any Runnable or Callable passed in by the submit() method is encapsulated as a FutureTask Callable. This class is a Runnable and the call() method is executed.

2. The return value is different

The execute() method is used to submit tasks that do not require a return value, so there is no way to determine whether the task was successfully executed by the thread pool;

The submit() method is used to submit tasks that require a return value. The thread pool returns an object of type Future that determines whether the task was successfully executed, and the return value can be retrieved from the Future’s get() method. The get() method blocks the current thread until the task is complete. Instead, get(long timeout, The TimeUnit unit method blocks the current thread for a while and then returns immediately, possibly without completing the task.

3. Exception handling is different

The execute() method throws an exception directly, while the submit() method does not.

Unlike Execute, Submit does not throw an exception but stores the exception in a member variable and throws the exception when futureTask.get blocks the acquisition.

Execute raises the exception directly and the thread dies. Submit saves that the exception thread does not die.

The principle of ThreadPoolExector is explained

Look directly at the execute() method

public void execute(Runnable command) {
        if (command == null) {
            throw new NullPointerException();
        } else{ int c = this.ctl.get(); // CTL is a variable of type AtomicInteger that holds the state of the thread pool and the number of workers running.if(workerCountOf(c) < this.corePoolsize) {// If the number of threads currently running is less than the number of coresif (this.addWorker(command.true//addWorker() creates a thread and executes the taskreturn; } c = this.ctl.get(); // Get the CTL again because concurrency is a concern}if (isRunning(c) && this.workQueue.offer(commandInt recheck = this.ctl.get();if(! isRunning(recheck) && this.remove(command)) {
                    this.reject(command);
                } else if (workerCountOf(recheck) == 0) {
                    this.addWorker((Runnable)null, false); }}else if(! this.addWorker(command.false) {// Create a new thread when the queue is full. If the number of threads reaches the maximum, execute this.reject(command); }}}Copy the code

AtomicInteger CTL attribute, CTL is the control state of the thread pool, used to represent the running state of the thread pool (the higher 3 bits of integer) and the number of running workers (the lower 29 bits), with the following 5 states:

  • RUNNING RUNNING state
  • SHUTDOWN Shuts down. No new tasks are accepted, but tasks in the queue continue to be processed.
  • STOP Stops. No new tasks are accepted, tasks in the queue are not processed, and ongoing tasks are interrupted
  • All working threads in TIDYING are terminated and the number of working threads terminated is 0. The terminated method is called and terminated
  • TERMINATED state

The workQueue cache has no Runnable that is executed immediately.

 private final class Worker extends AbstractQueuedSynchronizer implements Runnable
Copy the code

Worker rewrites the corresponding function of AQS and the run function of Runnable. It is understood to be the thread that performs the task in the thread pool. Its run() method executes its current task or task fetched from the queue.

Workers is a collection of Worker encapsulating Runnable, realizing the Runnable interface. Workers is the core implementation of the real thread pool. It is a HashSet that saves the tasks currently being executed and waiting to be executed.