Java concurrency-thread pool

Original author: the Matrix Hai Zi 

Original address: Java concurrent programming: the use of thread pool

 

In the previous article, we created a thread when we used a thread. This is very easy to implement, but there will be a problem:

  If there are a large number of concurrent threads, and each thread executes a short-time task and ends, then frequent thread creation will greatly reduce the efficiency of the system, because it takes time to create and destroy threads frequently.

  So is there a way to make threads reusable, that is, after executing a task, it will not be destroyed, but can continue to perform other tasks?

  In Java, this effect can be achieved through a thread pool. Today we will explain the thread pool in Java in detail. First we start with the methods in the core ThreadPoolExecutor class, then explain its implementation principles, then give examples of its use, and finally discuss how to configure it reasonably The size of the thread pool.

1. ThreadPoolExecutor class in Java

  The java.uitl.concurrent.ThreadPoolExecutor class is the core class in the thread pool, so if you want to thoroughly understand the thread pool in Java, you must first understand this class. Let's take a look at the specific implementation source code of the ThreadPoolExecutor class.

  Four construction methods are provided in the ThreadPoolExecutor class:

public class ThreadPoolExecutor extends AbstractExecutorService {
    .....
    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);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    ...
}

As can be seen from the above code, ThreadPoolExecutor inherits the AbstractExecutorService class and provides four constructors. In fact, by observing the specific implementation of the source code of each constructor, it is found that the first three constructors are the fourth constructors called Initialization work performed by the device.

   The following explains the meaning of each parameter in the constructor:

  • corePoolSize: The size of the core pool. This parameter has a great relationship with the implementation principle of the thread pool described later. After the thread pool is created, by default, there is no thread in the thread pool. Instead, it waits for tasks to arrive before creating threads to perform tasks, unless the prestartAllCoreThreads() or prestartCoreThread() method is called. From these two methods As you can see from the name, it means pre-created threads, that is, corePoolSize threads or one thread are created before no tasks arrive. By default, after the thread pool is created, the number of threads in the thread pool is 0. When a task comes, a thread will be created to execute the task. When the number of threads in the thread pool reaches corePoolSize, it will reach Place the tasks in the cache queue;
  • maximumPoolSize: the maximum number of threads in the thread pool, this parameter is also a very important parameter, it indicates the maximum number of threads that can be created in the thread pool;
  • keepAliveTime: Indicates how long the thread keeps at most when there is no task execution will terminate. By default, keepAliveTime will work only when the number of threads in the thread pool is greater than corePoolSize, until the number of threads in the thread pool is not greater than corePoolSize, that is, when the number of threads in the thread pool is greater than corePoolSize, if a thread is idle When the time reaches keepAliveTime, it will be terminated until the number of threads in the thread pool does not exceed corePoolSize. But if the allowCoreThreadTimeOut(boolean) method is called, when the number of threads in the thread pool is not greater than corePoolSize, the keepAliveTime parameter will also work until the number of threads in the thread pool is 0;
  • unit: The time unit of the parameter keepAliveTime. There are 7 values. There are 7 static properties in the TimeUnit class:
TimeUnit.DAYS;               //天
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒
  • workQueue: A blocking queue used to store tasks waiting to be executed. The choice of this parameter is also very important and will have a significant impact on the running process of the thread pool. Generally speaking, there are several options for the blocking queue here:
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;

  ArrayBlockingQueue and PriorityBlockingQueue are less used, and LinkedBlockingQueue and Synchronous are generally used. The queuing strategy of the thread pool is related to BlockingQueue.

  • threadFactory: thread factory, mainly used to create threads;
  • handler: Indicates the strategy when the task is refused to be processed, with the following four values:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 

   The relationship between the configuration of specific parameters and the thread pool will be described in the next section.

  From the code of the ThreadPoolExecutor class given above, we can know that ThreadPoolExecutor inherits AbstractExecutorService. Let's take a look at the implementation of AbstractExecutorService:

public abstract class AbstractExecutorService implements ExecutorService {
 
     
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { };
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { };
    public Future<?> submit(Runnable task) {};
    public <T> Future<T> submit(Runnable task, T result) { };
    public <T> Future<T> submit(Callable<T> task) { };
    private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
                            boolean timed, long nanos)
        throws InterruptedException, ExecutionException, TimeoutException {
    };
    public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException {
    };
    public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                           long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
    };
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException {
    };
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                         long timeout, TimeUnit unit)
        throws InterruptedException {
    };
}

AbstractExecutorService is an abstract class that implements the ExecutorService interface.

  We then look at the implementation of the ExecutorService interface:

public interface ExecutorService extends Executor {
 
    void shutdown();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;
 
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

And ExecutorService inherits the Executor interface, let's take a look at the implementation of the Executor interface:

public interface Executor {
    void execute(Runnable command);
}

   At this point, everyone should understand the relationship between ThreadPoolExecutor, AbstractExecutorService, ExecutorService and Executor .

  Executor is a top-level interface, in which only one method execute(Runnable) is declared, the return value is void, and the parameter is of type Runnable. It can be understood from the literal meaning that it is used to execute the task passed in;

  Then the ExecutorService interface inherits the Executor interface and declares some methods: submit, invokeAll, invokeAny, shutDown, etc.;

  The abstract class AbstractExecutorService implements the ExecutorService interface and basically implements all the methods declared in the ExecutorService;

  Then ThreadPoolExecutor inherits the class AbstractExecutorService.

  There are several very important methods in the ThreadPoolExecutor class:

execute()
submit()
shutdown()
shutdownNow()

   The execute() method is actually a method declared in Executor. It is implemented in ThreadPoolExecutor. This method is the core method of ThreadPoolExecutor. Through this method, a task can be submitted to the thread pool and executed by the thread pool.

  The submit() method is a method declared in ExecutorService. It has been implemented in AbstractExecutorService. It is not rewritten in ThreadPoolExecutor. This method is also used to submit tasks to the thread pool, but it and execute( ) Method is different, it can return the result of the task execution, look at the implementation of the submit() method, you will find that it is actually the execute() method called, but it uses Future to get the task execution result (Future related content will In the next article).

  shutdown() and shutdownNow() are used to close the thread pool.

  There are many other methods:

  For example: getQueue(), getPoolSize(), getActiveCount(), getCompletedTaskCount() and other methods to obtain attributes related to the thread pool. Friends who are interested can consult the API by themselves.

2. In-depth analysis of the principle of thread pool implementation

  In the previous section, we introduced ThreadPoolExecutor from a macro level. Let’s analyze the specific implementation principle of thread pool in depth, and explain from the following aspects: thread pool state, task execution, thread initialization in thread pool, task cache Queue and queuing strategy, task rejection strategy, thread pool closure, dynamic adjustment of thread pool capacity

1. Thread pool status

  A volatile variable is defined in ThreadPoolExecutor, and several static final variables are defined to represent the various states of the thread pool:

//表示当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性;
volatile int runState;

//下面的几个static final变量表示runState可能的几个取值。

//当创建线程池后,初始时,线程池处于RUNNING状态;
static final int RUNNING    = 0;

//如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
static final int SHUTDOWN   = 1;

//如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
static final int STOP       = 2;

//当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。
static final int TERMINATED = 3;

2. Task execution

  Before understanding the entire process from submitting the task to the thread pool to the completion of the task execution, let's take a look at some other important member variables in the ThreadPoolExecutor class:

private final BlockingQueue<Runnable> workQueue;              //任务缓存队列,用来存放等待执行的任务
private final ReentrantLock mainLock = new ReentrantLock();   //线程池的主要状态锁,对线程池状态(比如线程池大小、runState等)的改变都要使用这个锁
private final HashSet<Worker> workers = new HashSet<Worker>();  //用来存放工作集
 
private volatile long  keepAliveTime;    //线程存活时间   
private volatile boolean allowCoreThreadTimeOut;   //是否允许为核心线程设置存活时间
private volatile int   corePoolSize;     //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
private volatile int   maximumPoolSize;   //线程池最大能容忍的线程数
 
private volatile int   poolSize;       //线程池中当前的线程数
 
private volatile RejectedExecutionHandler handler; //任务拒绝策略
 
private volatile ThreadFactory threadFactory;   //线程工厂,用来创建线程
 
private int largestPoolSize;   //用来记录线程池中曾经出现过的最大线程数
 
private long completedTaskCount;   //用来记录已经执行完毕的任务个数

The role of each variable has been marked out, here we will focus on explaining the corePoolSize, maximumPoolSize, and largestPoolSize three variables.

corePoolSize is translated into core pool size in many places. In fact, my understanding is the size of thread pool. To give a simple example: if there is a factory, there are 10 workers in the factory, and each worker can only do one task at a time. Therefore, as long as there are workers among the 10 workers who are idle, the tasks that come will be assigned to the idle workers; when 10 workers have tasks to do, if there are still tasks, the tasks will be queued; if you say The number of new tasks is growing at a much faster rate than workers can do tasks. At this time, the factory supervisor may want to remedy measures, such as recruiting 4 temporary workers again; then assign tasks to these 4 temporary workers; if Speaking of the task speed of 14 workers is still not enough, at this time the factory supervisor may have to consider not accepting new tasks or abandoning some of the previous tasks. When some of these 14 workers are free, and the growth rate of new tasks is relatively slow, the factory supervisor may consider quitting 4 temporary workers and only keep the original 10 workers. After all, it costs money to hire additional workers. of.

The corePoolSize in this example is 10, and the maximumPoolSize is 14 (10+4). In other words, corePoolSize is the size of the thread pool. In my opinion, the maximumPoolSize is a remedy for the thread pool, that is, a remedy when the task volume is suddenly too large. However, in order to facilitate understanding, corePoolSize will be translated into core pool size later in this article.

largestPoolSize is just a variable used for recording, used to record the largest number of threads ever in the thread pool, and has nothing to do with the capacity of the thread pool.

Let's move on to the topic and take a look at the process of the task from submission to final execution.

In the ThreadPoolExecutor class, the core task submission method is the execute() method. Although the task can be submitted through submit, in fact, the final call in the submit method is the execute() method, so we only need to study the execute() method The realization principle can be:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
        if (runState == RUNNING && workQueue.offer(command)) {
            if (runState != RUNNING || poolSize == 0)
                ensureQueuedTaskHandled(command);
        }
        else if (!addIfUnderMaximumPoolSize(command))
            reject(command); // is shutdown or saturated
    }
}

The above code may not seem so easy to understand, let's explain it sentence by sentence:

First, determine whether the submitted task command is null, if it is null, a null pointer exception will be thrown;

Followed by this sentence, this sentence should be understood:

if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command))

Because it is an or conditional operator, the first half of the value is calculated first. If the current number of threads in the thread pool is not less than the core pool size, then the following if statement block will be entered directly.

If the current number of threads in the thread pool is less than the core pool size, then execute the second half, which is to execute

addIfUnderCorePoolSize(command)

  If the addIfUnderCorePoolSize method returns false after executing, continue to execute the following if statement block, otherwise the entire method will be executed directly.

  If the addIfUnderCorePoolSize method returns false after executing, then continue to judge:

if (runState == RUNNING && workQueue.offer(command))

If the current thread pool is in the RUNNING state, the task is put into the task cache queue; if the current thread pool is not in the RUNNING state or the task fails to be put into the cache queue, execute:

addIfUnderMaximumPoolSize(command)

  If the addIfUnderMaximumPoolSize method fails, the reject() method is executed to reject the task.

  Back to the front:

if (runState == RUNNING && workQueue.offer(command))

   The execution of this sentence, if it is said that the current thread pool is in the RUNNING state and the task is successfully placed in the task cache queue, then continue to judge:

if (runState != RUNNING || poolSize == 0)

   This sentence is an emergency measure to prevent other threads from suddenly calling the shutdown or shutdownNow method to close the thread pool while adding this task to the task cache queue. If so, execute:

ensureQueuedTaskHandled(command)

   For emergency treatment, it can be seen from the name to ensure that the tasks added to the task cache queue are processed.

  We then look at the implementation of two key methods: addIfUnderCorePoolSize and addIfUnderMaximumPoolSize:

private boolean addIfUnderCorePoolSize(Runnable firstTask) {
    Thread t = null;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (poolSize < corePoolSize && runState == RUNNING)
            t = addThread(firstTask);        //创建线程去执行firstTask任务   
        } finally {
        mainLock.unlock();
    }
    if (t == null)
        return false;
    t.start();
    return true;
}

This is the specific implementation of the addIfUnderCorePoolSize method. From the name, it can be seen that its intention is to execute the method when the size is lower than the core size. Let's look at its specific implementation. First, the lock is acquired, because this place involves the change of the thread pool state. First, use the if statement to determine whether the number of threads in the current thread pool is less than the core pool size. Some friends may have questions: () Hasn't it been judged in the method? The addIfUnderCorePoolSize method will be executed only when the current number of threads in the thread pool is less than the core pool size. Why does this place continue to judge? The reason is very simple. There is no lock in the previous judgment process, so the poolSize may be less than corePoolSize when the execute method judges, and after the judgment is completed, tasks are submitted to the thread pool in other threads, which may cause the poolSize to be no less than corePoolSize So you need to continue to judge in this place. Then determine whether the state of the thread pool is RUNNING, the reason is also very simple, because it is possible to call the shutdown or shutdownNow method in other threads. Then execute

t = addThread(firstTask);

   This method is also very critical. The parameter passed in is the submitted task, and the return value is of type Thread. Then next judge whether t is empty. If it is empty, it indicates that the thread creation failed (ie poolSize>=corePoolSize or runState is not equal to RUNNING), otherwise the t.start() method is called to start the thread.

  Let's take a look at the implementation of the addThread method:

private Thread addThread(Runnable firstTask) {
    Worker w = new Worker(firstTask);
    Thread t = threadFactory.newThread(w);  //创建一个线程,执行任务   
    if (t != null) {
        w.thread = t;            //将创建的线程的引用赋值为w的成员变量       
        workers.add(w);
        int nt = ++poolSize;     //当前线程数加1       
        if (nt > largestPoolSize)
            largestPoolSize = nt;
    }
    return t;
}

   In the addThread method, first create a Worker object with the submitted task, then call the thread factory threadFactory to create a new thread t, and then assign the thread t reference to the member variable thread of the Worker object, and then pass workers.add (w) Add the Worker object to the working set.

  Let's take a look at the implementation of the Worker class:

private final class Worker implements Runnable {
    private final ReentrantLock runLock = new ReentrantLock();
    private Runnable firstTask;
    volatile long completedTasks;
    Thread thread;
    Worker(Runnable firstTask) {
        this.firstTask = firstTask;
    }
    boolean isActive() {
        return runLock.isLocked();
    }
    void interruptIfIdle() {
        final ReentrantLock runLock = this.runLock;
        if (runLock.tryLock()) {
            try {
        if (thread != Thread.currentThread())
        thread.interrupt();
            } finally {
                runLock.unlock();
            }
        }
    }
    void interruptNow() {
        thread.interrupt();
    }
 
    private void runTask(Runnable task) {
        final ReentrantLock runLock = this.runLock;
        runLock.lock();
        try {
            if (runState < STOP &&
                Thread.interrupted() &&
                runState >= STOP)
            boolean ran = false;
            beforeExecute(thread, task);   //beforeExecute方法是ThreadPoolExecutor类的一个方法,没有具体实现,用户可以根据
            //自己需要重载这个方法和后面的afterExecute方法来进行一些统计信息,比如某个任务的执行时间等           
            try {
                task.run();
                ran = true;
                afterExecute(task, null);
                ++completedTasks;
            } catch (RuntimeException ex) {
                if (!ran)
                    afterExecute(task, ex);
                throw ex;
            }
        } finally {
            runLock.unlock();
        }
    }
 
    public void run() {
        try {
            Runnable task = firstTask;
            firstTask = null;
            while (task != null || (task = getTask()) != null) {
                runTask(task);
                task = null;
            }
        } finally {
            workerDone(this);   //当任务队列中没有任务时,进行清理工作       
        }
    }
}

It actually implements the Runnable interface, so the above Thread t = threadFactory.newThread(w); has basically the same effect as the following sentence:

Thread t = new Thread(w);

   It is equivalent to passing in a Runnable task and executing this Runnable in thread t.

  Now that Worker implements the Runnable interface, the core method is naturally the run() method:

public void run() {
    try {
        Runnable task = firstTask;
        firstTask = null;
        while (task != null || (task = getTask()) != null) {
            runTask(task);
            task = null;
        }
    } finally {
        workerDone(this);
    }
}

   It can be seen from the implementation of the run method that it first executes the task firstTask passed in through the constructor. After calling runTask() to execute the firstTask, it continuously uses getTask() in the while loop to fetch new tasks for execution. So where to get it? Naturally, it is taken from the task cache queue. getTask is a method in the ThreadPoolExecutor class, not a method in the Worker class. The following is the implementation of the getTask method:

Runnable getTask() {
    for (;;) {
        try {
            int state = runState;
            if (state > SHUTDOWN)
                return null;
            Runnable r;
            if (state == SHUTDOWN)  // Help drain queue
                r = workQueue.poll();
            else if (poolSize > corePoolSize || allowCoreThreadTimeOut) //如果线程数大于核心池大小或者允许为核心池线程设置空闲时间,
                //则通过poll取任务,若等待一定的时间取不到任务,则返回null
                r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
            else
                r = workQueue.take();
            if (r != null)
                return r;
            if (workerCanExit()) {    //如果没取到任务,即r为null,则判断当前的worker是否可以退出
                if (runState >= SHUTDOWN) // Wake up others
                    interruptIdleWorkers();   //中断处于空闲状态的worker
                return null;
            }
            // Else retry
        } catch (InterruptedException ie) {
            // On interruption, re-check runState
        }
    }
}

   In getTask, first determine the current thread pool state, if runState is greater than SHUTDOWN (that is, STOP or TERMINATED), then directly return null.

  If runState is SHUTDOWN or RUNNING, the task is fetched from the task cache queue.

  If the number of threads in the current thread pool is greater than the core pool size corePoolSize or the idle survival time is allowed to be set for the threads in the core pool, call poll(time, timeUnit) to fetch the task. This method will wait for a certain time. If the task cannot be fetched It returns null.

  Then judge whether the retrieved task r is null. If it is null, judge whether the current worker can exit by calling the workerCanExit() method. Let’s look at the implementation of workerCanExit():

private boolean workerCanExit() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    boolean canExit;
    //如果runState大于等于STOP,或者任务缓存队列为空了
    //或者  允许为核心池线程设置空闲存活时间并且线程池中的线程数目大于1
    try {
        canExit = runState >= STOP ||
            workQueue.isEmpty() ||
            (allowCoreThreadTimeOut &&
             poolSize > Math.max(1, corePoolSize));
    } finally {
        mainLock.unlock();
    }
    return canExit;
}

   That is to say, if the thread pool is in the STOP state, or the task queue is empty, or the idle survival time is allowed to be set for the core pool threads and the number of threads is greater than 1, the worker is allowed to exit. If the worker is allowed to exit, call interruptIdleWorkers() to interrupt the idle worker. Let’s take a look at the implementation of interruptIdleWorkers():

void interruptIdleWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers)  //实际上调用的是worker的interruptIfIdle()方法
            w.interruptIfIdle();
    } finally {
        mainLock.unlock();
    }
}

   It can be seen from the implementation that it actually calls the worker's interruptIfIdle() method, in the worker's interruptIfIdle() method:

void interruptIfIdle() {
    final ReentrantLock runLock = this.runLock;
    if (runLock.tryLock()) {    //注意这里,是调用tryLock()来获取锁的,因为如果当前worker正在执行任务,锁已经被获取了,是无法获取到锁的
                                //如果成功获取了锁,说明当前worker处于空闲状态
        try {
    if (thread != Thread.currentThread())  
    thread.interrupt();
        } finally {
            runLock.unlock();
        }
    }
}

    There is a very clever design method. If we design a thread pool, there may be a task dispatch thread. When a thread is found to be idle, a task is taken from the task cache queue to the idle thread for execution. But here, this method is not adopted, because this will additionally manage the task dispatching thread, which will invisibly increase the difficulty and complexity. Here, the thread that has completed the task is directly sent to the task cache queue to fetch the task for execution. .

   Let's look at the implementation of the addIfUnderMaximumPoolSize method. The implementation idea of ​​this method is very similar to the implementation idea of ​​the addIfUnderCorePoolSize method. The only difference is that the addIfUnderMaximumPoolSize method is that the number of threads in the thread pool reaches the core pool size and adding tasks to the task queue fails. Under the circumstances:

private boolean addIfUnderMaximumPoolSize(Runnable firstTask) {
    Thread t = null;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (poolSize < maximumPoolSize && runState == RUNNING)
            t = addThread(firstTask);
    } finally {
        mainLock.unlock();
    }
    if (t == null)
        return false;
    t.start();
    return true;
}

   See if it is not, in fact, it is basically the same as the implementation of the addIfUnderCorePoolSize method, except that the poolSize <maximumPoolSize in the judgment condition of the if statement is different.

  At this point, most of my friends should have a basic understanding of the entire process from when the task is submitted to the thread pool to being executed. Here is a summary:

  1) First, we must be clear about the meaning of corePoolSize and maximumPoolSize;

  2) Secondly, we must know what the Worker is used for;

  3) To know the processing strategy after the task is submitted to the thread pool, here are four main points:

  • If the number of threads in the current thread pool is less than corePoolSize, every time a task comes, a thread will be created to execute the task;
  • If the number of threads in the current thread pool >=corePoolSize, then every time a task comes, it will try to add it to the task cache queue. If the addition is successful, the task will wait for the idle thread to take it out for execution; if the addition fails ( Generally speaking, the task cache queue is full), it will try to create a new thread to execute this task;
  • If the number of threads in the current thread pool reaches the maximumPoolSize, the task rejection strategy will be adopted for processing;
  • If the number of threads in the thread pool is greater than corePoolSize, if the idle time of a thread exceeds keepAliveTime, the thread will be terminated until the number of threads in the thread pool is not greater than corePoolSize; if it is allowed to set the survival time for the threads in the core pool, then the core pool If the idle time of the thread exceeds keepAliveTime, the thread will also be terminated.

3. Thread initialization in the thread pool

  By default, after the thread pool is created, there are no threads in the thread pool, and the thread will be created after the task is submitted.

  In practice, if you need to create threads immediately after the thread pool is created, you can do it in the following two ways:

  • prestartCoreThread(): Initialize a core thread;
  • prestartAllCoreThreads(): initialize all core threads

  The following is the implementation of these two methods:

public boolean prestartCoreThread() {
    return addIfUnderCorePoolSize(null); //注意传进去的参数是null
}
 
public int prestartAllCoreThreads() {
    int n = 0;
    while (addIfUnderCorePoolSize(null))//注意传进去的参数是null
        ++n;
    return n;
}

 

Guess you like

Origin blog.csdn.net/sanmi8276/article/details/112779056