[Logic of Java Programming] Thread Pool

basic introduction

A thread pool, as the name implies, is a pool of threads, and there are several threads in it. Their purpose is to execute tasks submitted to the thread pool. After executing a task, it will not exit, but continue to wait or execute new tasks.

Advantages of thread pools:

  • Threads can be reused, avoiding the overhead of thread creation
  • When there are too many tasks, avoid creating too many threads through sorting, reduce system resource consumption and competition, and ensure that tasks are completed in an orderly manner

ThreadPoolExecutor

The implementation class of the thread pool in the Java concurrent package is ThreadPollExecutor, which inherits AbstractExecutorService, which implements ExecutorService

Let's first look at the main construction method of ThreadPoolExecutor

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

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

Let's talk about these parameters first

thread pool size

  • corePoolSize
  • maximumPoolSize
  • keepAliveTime 和 unit

corePollSize indicates the number of core threads in the thread pool, not so many threads are created at the beginning. By default, no threads are created after a thread pool is created. When the number of threads is less than or equal to corePoolSize, we call these threads core threads

maximumPoolSize indicates the maximum number of threads in the thread pool, the number of threads will change dynamically, but will not create a number of threads larger than this value

Under normal circumstances, when a new task arrives, if the current number of threads is less than corePoolSize, a new thread will be created to execute the task, even if other threads are now idle. If the number of threads is greater than or equal to corePoolSize, it will not create new threads immediately, but try to queue first, if the queue is full or cannot be queued immediately for other reasons, it will not queue, but check whether the number of threads has reached maximumPoolSize, if not, threads will be created.

keepAliveTime indicates the survival time of additional idle threads when the number of threads in the thread pool is greater than corePoolSize. That is to say, a non-core thread will have a maximum waiting time (keepAliveTime) when it is idle waiting for a new task. If there is no new task at the time, it will be terminated. If the value is 0, it means that all threads will not time out to terminate.

By default: core threads are not pre-created, only created when there are tasks; core threads are not terminated due to idleness.
However, ThreadPoolExecutor can change this default behavior

// 预先创建所有的核心线程
public int prestartAllCoreThreads();
// 创建一个核心线程,如果所有核心线程都已创建,返回false
public boolean prestartCoreThread();
// 如果设置为true,则keepAliveTime参数也适用于核心线程
public void allowCoreThreadTimeout(boolean value);

queue

ThreadPoolExecutor requires that the queue type is BlockingQueue.
When introducing concurrent containers , I learned about several blocking queues:

  • LinkedBlockingQueue: Blocking queue based on linked list, the maximum length can be specified, but the default is unbounded
  • ArrayBlockingQueue: Array-based bounded blocking queue
  • PriorityBlockingQueue: heap-based unbounded blocking queue
  • SynchronousQueue: A synchronous blocking queue with no actual storage space

rejection policy

If the queue is bounded and the maximumPoolSize is limited, when the queue is full and the number of threads reaches the maximumPoolSize, a new task will be triggered, and the task rejection strategy of the thread pool will be triggered.
ThreadPoolExecutor implements four processing methods:
1. ThreadPoolExecutor. AbortPolicy: This is the default method, throws an exception RejectedExecutionException
2. ThreadPoolExecutor.DiscardPolicy: Ignore new tasks, do not throw exceptions, and do not execute
3. ThreadPoolExecutor.DiscardOldestPolicy: Throw away the longest waiting task, and then queue
4. ThreadPoolExecutor.CallerRunsPolicy: Execute the task in the task submitter thread, rather than hand it over to the thread in the thread pool for execution

They are all inner classes of ThreadPoolExecutor and implement the RejectedExecutionHandler interface

public interface RejectedExecutionHandler { 
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

thread factory

There is one more parameter: ThreadFactory. it is an interface

public interface ThreadFactory {
    Thread newThread(Runnable r);
}

This interface creates a Thread based on Runnable.
The default implementation of ThreadPoolExecutor is the static inner class DefaultThreadFacotry in the Executors class. The main purpose is to create a thread, set a name for the thread, set the daemon attribute to false, and set the thread priority to the standard default priority. The thread name format is: pool-<thread pool number>-thread-<thread number>

static class DefaultThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                              Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                      poolNumber.getAndIncrement() +
                     "-thread-";
    }

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

Factory class Executors

The class Executors provides some static engineering methods to easily create some preconfigured thread pools

newSingleThreadExecutor

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

Only one thread is used, and an unbounded LinkedBlockingQueue is used. After the thread is created, it will not time out and terminate. The thread executes all tasks sequentially. This thread is suitable for situations where you need to ensure that all tasks are executed sequentially

newFixedThreadPool

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

Using a fixed number of n threads and using unbounded LinkedBlockingQueue, the thread will not be terminated by timeout after creation. Because it is an unbounded queue, if there are too many queued tasks, it will consume too much memory.

newCachedThreadPool

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

corePoolSize is 0, maximumPoolSize is Integer.MAX_VALUE, keepAliveTime is 60 seconds, and the queue is SynchronousQueue.
When a new task comes, if there is an idle thread waiting for the task, one of the idle threads accepts the task, otherwise a new thread is always created, and the total number of threads created is not limited. For idle threads, if Terminate if there are no new tasks within 60 seconds

Deadlock in thread pool

For tasks submitted to the thread pool, if there are dependencies between tasks, deadlock may occur.

If task A is submitted to a single-threaded thread pool, during its execution, it submits a task B to the same task execution service, but needs to wait for task B to return a result, which will cause a deadlock.

Processing method: use newCachedThreadPool to create a thread pool, so that the number of threads is not limited; use SynchronousQueue as the waiting queue, for SynchronousQueue, the success of joining the queue means that the existing thread accepts processing, if the joining fails, you can create a new thread

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325854276&siteId=291194637