This article directory

directory

Why use thread pools?

Description of thread pool parameters

There are six common thread pools

Why can’t thread pools be created automatically

How do I customize the appropriate thread pool

How do I properly close a thread pool

Principle of thread pool reuse

conclusion

Why use thread pools?

Why use thread pools?

  • Creating threads repeatedly is costly, and it takes time to create and destroy each thread. If a task is relatively simple, the creation and destruction of threads may consume more resources than the execution of the task.

  • If a large number of tasks need to be executed, each thread is responsible for one task, so many threads need to be created to execute the task, too many threads will occupy too much memory resources, and also cause context switching, and system instability.

Thread pool benefits

  • Thread pool solves the system overhead problem of thread life cycle, thread pool can be used repeatedly, can use a small number of threads to perform a large number of tasks, reduce the overhead of thread creation and destruction, and threads are created, to the task can be executed.

  • By setting the appropriate thread pool threads, can avoid the improper use of resources, flexible thread pool can be by the number of threads and tasks control the number of threads and tasks can continue to create more threads, only when the task is little core thread, so that we can avoid the waste of system resources and thread too much lead to memory overflow.

  • The thread pool can manage resources in a unified way. Through thread books and task queues, it can start and end in a unified way, and set relevant rejection policies.

Description of thread pool parameters

This section describes the meanings of parameters in a thread pool

  • CorePoolSize: number of core threads, the number of threads in the resident thread pool

  • MaxPoolSize: specifies the maximum number of threads in the thread pool. If the number of corePoolSize threads cannot meet the requirements, threads will be created. The maximum number of threads will not exceed maxPoolSize.

  • KeepAliveTime+ Time unit: KeepAliveTime of idle threads

  • ThreadFactory: ThreadFactory, used to create threads

  • WorkQueue: a task queue for storing tasks

  • Handler: Processes rejected policies

Flow chart of thread pool processing tasks:

As shown in the figure above, the process is as follows:

  1. When a task is submitted, the thread pool first checks the number of current threads. If the number of current threads is smaller than the number of core threads, a new thread is created and the task is executed.

  2. As the number of tasks increases and the number of threads reaches the number of core threads, the number of tasks is still increasing. The new tasks will be placed in the workQueue, and the core thread will retrieve the tasks from the queue after completing the tasks

  3. If has reached the core number of threads, and task queue is full, the thread pool will continue to create a thread to perform the task, if a task submitted constantly, the thread pool will continue to create a thread until it reaches an maximumPoolSize maximum number of threads, when reached the maximum number of threads, task continues to submit, so this is more than the maximum thread pool can handle Force, at which point the thread pool will refuse to process these tasks, and the processing strategy is handler.

CorePoolSize and maximumPoolSize:

Can be seen from the above process, thread pool initialization, the number of threads by default is 0, when a task is submitted, and start creating core thread to perform the task, when the number of threads to achieve the core number of threads and task queue is full, start creating non-core threads to execute tasks, the biggest can reach maximumPoolSize, if this task is not submitted, line By default, threads larger than corePoolSize are reasonably called back when they exceed the KeepAliveTime limit, so by default, the number of threads in the thread pool is between corePoolSize and maximumPoolSize.

KeepAliveTime+ time unit:

By default, when a number more than the core number of threads in thread pool, and is there a task to do, the thread pool will detect KeepAliveTime threads, if over the stipulated time, has nothing to do the thread is destroyed, in order to reduce memory footprint and resource consumption, if late tasks and more up, the thread pool according to the rules This is a scalable way to make use of resources by recreating threads. We can setKeepAliveTime by setKeepAliveTime or allowCoreThreadTimeOut, which is false by default if set If true, a timeout is set for the number of core threads. If the timeout is exceeded, the core thread is destroyed.

ThreadFactory:

ThreadFactory is a ThreadFactory that produces threads to perform tasks. The default ThreadFactory creates threads that are in the same thread group, have the same priority, and are not daemons. We can also customize thread factories to give custom names to threads.

WorkQueue:

Blocking queues are used to store tasks. Let’s analyze five types of blocking queues:

  1. ArrayBlockingQueue is an array-based bounded blocking queue, sorted by FIFO, in which new queues are placed at the end of the queue. A bounded array prevents the queue from running out of resources. When a thread reaches the core number of threads, it is placed at the end of the queue When the number of threads reaches maxPoolSize, the reject policy is implemented.

  2. LinkedBlockingQueue is an unbounded queue based on a linked list (the maximum size is integer.max) that is sorted by FIFO. When the number of threads in the thread pool reaches the core number of threads, new tasks are stored in the queue instead of creating new threads. Therefore,maxPoolSize cannot be done when using this queue

  3. SynchronousQueue is a blocking queue that does not cache tasks. When a new task comes in, it is not cached in the queue but executed by a thread. If no core thread is available, a new thread is created to execute the task.

  4. PriorityBlockingQueue is an unbounded blocking queue with a priority, which is implemented using the Comparator parameter

  5. The DelayedWorkQueu queue is characterized by the fact that tasks are not sorted by the time they are put in, but by the amount of time they are delayed, using a “heap” data structure. And it’s also an unbounded queue.

Handler:

RejectedExecutionHandler RejectedExecutionHandler RejectedExecutionHandler RejectedExecutionHandler RejectedExecutionHandler RejectedExecutionHandler RejectedExecutionHandler RejectedExecutionHandler RejectedExecutionHandler Mouth:

  1. CallRunsPolicy: In this policy, the run method of the rejected task is directly executed in the caller thread. That is, the person who submits the task is responsible for executing the task. In this way, the task will not be lost, and the execution of the task is time-consuming, so that the submitting thread will be occupied, which can slow down the task submission speed.

  2. AbortPolicy: under this strategy, abandon the task directly, and RejectedExecutionException anomalies.

  3. DiscardPolicy: Discards tasks.

  4. DiscardOldestPolicy: In this policy, a user discards the earliest task and attempts to queue the rejected task.

In addition to that, We can also implement RejectedExecutionHandler Interface to implement the rejectedExecution policy. In this interface, you need to implement the rejectedExecution method. In the rejectedExecution method, you can execute customized rejection policies, such as temporary tasks and re-execution.

There are six common thread pools

FixedThreadPool

The number of core threads in this thread pool is the same as the maximum number of threads, so it can be regarded as a fixed number of threads pool. The characteristic is that when the number of threads reaches the core number, if the task queue is full, no additional non-core threads are created to execute the task, but the rejection policy is implemented.

CachedThreadPool

This thread pool, called the cache thread pool, is characterized by an almost infinite number of threads (the maximum value is integer.max_value, which is rarely reached) that can be reclaimed when idle, and is stored in a SynchronousQueue, which has a capacity of 0 and no actual storage It is only responsible for the transfer and transmission of tasks, so a task thread pool to see whether there is a free thread, if there is a free thread to execute tasks, otherwise it will create a thread to execute, high efficiency.

ScheduledThreadPool

As the name suggests, the thread pool supports timed or periodic execution of tasks, which can be done in three main ways:

ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
service.schedule(new Task(), 10, TimeUnit.SECONDS);
service.scheduleAtFixedRate(new Task(), 10, 10, TimeUnit.SECONDS);
service.scheduleWithFixedDelay(new Task(), 10, 10, TimeUnit.SECONDS);
Copy the code

The first type is schedule, which is used to delay the execution of a task after the specified time. The code is set to 10 seconds, so the execution of a task after 10 seconds is finished.

The second one is scheduleAtFixedRate. We can see from its name that the second one executes tasks at a fixed frequency. Its second parameter initialDelay indicates the time of the first delay, and the third parameter period indicates the period The task is then executed with a delay of 10 seconds at a time.

The third method is scheduleWithFixeddelay, which is similar to the second method and also executes tasks periodically, but the difference is the definition of the period. The previous scheduleAtFixedRate starts with the start time of a task, and the second task is executed when the time is up, regardless of the task needs How long to execute, and scheduleWithFixeddelay takes the time when the task ends as the starting point of the next cycle.

SingleThreadExecutor

The fourth thread pool has only one thread to execute the task. If an exception occurs during the task execution, the thread pool will create a new thread to execute the subsequent task. Since there is only one thread, the task execution can be orderly.

SingleThreadScheduleExecutor

This thread pool is similar to the ScheduledThreadPool, except that it has only one thread inside. It sets the number of core threads to 1 and creates a new thread to execute the task if an exception occurs during execution.

ForkJoinPool

The final thread pool, ForkJoinPool, is designed to support parallel computation by splitting a task into smaller tasks. This thread pool was added after jdk1.7 and is used to implement divide-and-conquer algorithms, especially for recursive calls to divide-and-conquer functions. ForkJoinPool This is a brief introduction to ForkJoinPool, but we will introduce two main features of ForkJoinPool and its predecessors.

The first point is fork and join:

Fork is to decompose the task into multiple sub-tasks, which are independent of each other and not affected by each other. During execution, the multi-core advantage of CPU can be used for parallel calculation. After the calculation is completed, each sub-task can be summarized by calling join method. The second step is the summary, also known as the Join, which we can understand through the following figure:

We illustrate the use of this thread pool by illustrating the Fibonacci sequence.

FibonacciTask > FibonacciTask > FibonacciTask > FibonacciTask > FibonacciTask > FibonacciTask > FibonacciTask > FibonacciTask > FibonacciTask ForkJoinTask represents a parallel, merged task. ForkJoinTask is an abstract class with two abstract subclasses: RecusiveAction and RecusiveTask. Where RecusiveTask represents the task with a return value and RecusiveAction represents the task with no return value.

2. We compute the Fibonacci sequence and get the return value.

3. Create a ForkJoinPool in the main method, and call ForkJoinTasktask to obtain the return value of the calculated task.

Task class: FibonacciTask

* RecursiveAction and RecursiveTask are two abstract subclasses of ForkJoinTask. ForkJoinTask, which represents a parallel, joinable task, and RecursiveAction, which indicates a task that does not return a value, Public class FibonacciTask extends RecursiveTask<Integer> {private int I; FibonacciTask(int i){ this.i=i; } @Override protected Integer compute() { if(i<=1){ return i; } FibonacciTask f1=new FibonacciTask(i-1); // split tasks with fork() and execute f1.fork(); FibonacciTask f2=new FibonacciTask(i-2); f2.fork(); Return f1.join()+f2.join(); return f1.join()+f2.join(); }}Copy the code

The main method:

 public static void main(String[] args) {
        ForkJoinPool forkJoinPool=new ForkJoinPool();
        for(int i=0;i<10;i++){
            ForkJoinTask<Integer> task = forkJoinPool.submit(new FibonacciTask(i));
            try {
                System.out.println(task.get());
            } catch (InterruptedException e) { 
               e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
Copy the code

The calculation results are as follows:

The second point is the difference in internal structure:

In the previous thread pool, all threads in a ForkJoinPool share a single queue, but each thread in a ForkJoinPool has its own separate queue. This queue is double-ended, as shown in the following figure:

In addition to a common queue of tasks, each thread in the ForkJoinPool also has a double-ended queue deque. When a task is forked, the subtasks are placed in the thread’s own deque. If there are three subtasks in the deque queue of thread T1, the cost of acquiring the task for thread T1 is reduced. You can fetch directly from your own task queue without having to fight for it in the public queue and without blocking (except for steal), reducing contention and switching between threads and making it very efficient.

Let’s consider another case, at this time, there are multiple threads, and the task of thread T1 is extremely heavy, splitting dozens of sub-tasks, but T0 has nothing to do at this time, its own deque queue is empty, in order to improve efficiency, T0 will try to help T1 to perform the task. That’s what it means to work stealing.

In a double-ended queue deque, the logic of task acquisition by thread T1 is LIFO (Last In Frist Out), while the logic of task acquisition by thread T0 In “steal” deque by thread T1 is fifO. FIFO (Fast In Frist Out), as shown In the figure, is a good description of two threads using a double-ended queue to obtain tasks. As you can see, the use of the “work-stealing” algorithm and double-ended queues balances the load of each thread very nicely.

Finally, a comprehensive view of the internal structure of a ForkJoinPool thread pool is provided. You can see that the ForkJoinPool is similar in many ways to any other thread pool, but the main difference is that each thread has its own two-ended queue to store the subtasks it splits into. ForkJoinPool is ideal for recursive scenarios such as tree traversal, optimal path searching, and so on.

Why can’t thread pools be created automatically

First automatically create a thread pool by directly calling Executors. NewCachedThreadPool () method to create a thread pool directly. However, we cannot use the created thread pool directly in development for the following reasons:

FixedThreadPool

Through FiexdThreadPool internal code below, you can see that FixedThreadPool internal call is ThreadPoolExecutor constructor, the constructor yes the blocking queue is LinkedBlockingQueue, then it brings problems, when the task When the processing speed is relatively slow, although more and more new tasks are added, more and more tasks are accumulated in the queue, which will eventually occupy a lot of memory and OOM occurs, which will seriously affect the operation of the program.

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

SingleThreadExecutor

NewSingleThreadExecutor and newFixedThreadPool work in the same way, except that the core thread count and the maximum thread count are set to 1, but the task queue remains unbounded Li NkedBlockingQueue will also cause tasks to pile up and cause OOM problems.

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

CachedThreadPool

Continue to see below CachedThreadPool internal code, can be seen from the code, CachedThreadPool blocking queue is SynchronousQueue will use tasks, SynchronousQueue will queue we mentioned earlier, not storage task, only to direct the tasks This queue does not cause OOM problems, but the maximum number of threads is set to integer. MAX_VALUE, so the CachedThreadPool does not contain the number of threads.

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

ScheduledThreadPool and SingleThreadScheduledExector

ScheduledThreadPool and SingleThreadScheduledExector about, is only the latter is only one thread in the thread pool, ScheduledThreadPool source code is as follows:

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

We are into ScheduledThreadPoolExecutor construction method, can be seen from the chart, it USES the task queue is the DelayWorkQueue, we said that this queue above a delay queue is also an unbounded queue, so it and LinkedBlockingQueu E, if too many tasks may be OOM, the code is as follows:

public ScheduledThreadPoolExecutor(int corePoolSize) {
     super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue());
}
Copy the code

SingleThreadExecutor

The fourth thread pool has only one thread to execute the task. If an exception occurs during the task execution, the thread pool will create a new thread to execute the subsequent task. Since there is only one thread, the task execution can be orderly.

How do I customize the appropriate thread pool

This is also a question that will be asked in the interview, how about customizing the appropriate thread pool? The first step is to adjust the number of threads in the thread pool so that CPU and memory resources are fully and reasonably utilized to maximize performance.

CPU intensive task

If a task is a series of CPU resources consuming task, such as encryption, decryption, compression, calculation, etc., then the optimal number of threads is the number of CPU core 1 ~ 2 times, it lead to take up a lot of CPU resources, then each CPU core are basic work at full capacity, set up too many threads can cause unnecessary context switches, and lead to performance degradation, and And on the same machine, we also need to consider other programs that will occupy more CPU resources to run, and then do the overall balance.

Time-consuming I/O Tasks

For example, database, file reading and writing, network communication, and other tasks do not consume much CPU resources, but I/O operations are time-consuming. This time can set the maximum number of threads are generally greater than CPU core number of threads a lot of times, because IO speed compared to the CPU speed is slow, we set the number of threads, less will be waste of CPU resources, if set more threads, so part of the thread is waiting for IO, they don’t need a CPU right now, so you’ll have more threads to execute The IO operation reduces the number of waiting tasks in the task queue and makes more rational use of resources.

Java concurrent programming practice has recommended: number of threads = NUMBER of CPU cores *(1+ average waiting time/average working time), we can use this formula to calculate a reasonable number of threads, but also according to pressure measurement, monitoring JVM threads, etc., determine the number of threads, more reasonable use of resources.

The above characteristics can be summarized as follows:

The higher the percentage of average working time of threads, the fewer threads are required.

The higher the proportion of the average wait time for a thread, the more threads are required

For different programs, the corresponding actual test can obtain a more suitable choice.

How do I properly close a thread pool

First, there are five methods to close a thread that are involved in ThreadPoolExecutor, and we’ll look at them one by one.

void shutdown()

It is safe to close a thread pool, call shutdown () method, a thread pool is not immediately shut down, but wait after executing the task and the queue waiting for the task of completely closed, only after shutdown and call () method, if there is a new mission continue to come, the thread pool according to refuse strategy behind direct refused to come A new task.

boolean isShutdown()

This method can return true or false to determine whether the shutdown process has started, that is, whether the shutdown or shutdownNow methods have been executed. If the isShutdown() method returns true, it does not mean that the thread pool has been shutdown completely, but only that the shutdown process has started There may be threads executing tasks, and there may be tasks waiting to be executed in the queue.

boolean isTerminated()

This method checks whether the thread pool is actually closed, and whether all tasks in the thread pool have been completed. For example, the shutdown() method has been called, but one thread is executing a task, and isShutdown returns true, while isTer is called The minated method returns false, because there are more tasks to execute in the thread pool. The thread pool is not really closed until all threads have executed. IsTermainted returns true.

Boolean awaitTermination(Long timeout,TimeUnit Unit), throws IntereuptedException

AwaitTermination is not used to close the thread pool, but to determine the state of the thread pool. The parameter needs to be passed a time. If we set it to 10 seconds, the following situations will occur:

  1. While waiting, the thread pool is closed and all submitted tasks have been executed, the method returns true, which means the thread pool is actually closed.

  2. If the first condition does not occur after a timeout, the method returns false.

  3. The method throws InterruptedException while the thread executing the task is interrupted.

So it can be seen that after calling awaitTermination, the current thread will try to wait for a specified amount of time. If during the waiting time, the thread pool is closed and all internal tasks are completed, that is to say, the thread pool is truly “terminated”, then the method returns true. Otherwise, the timeout returns fasle, and we can determine what to do next based on the Boolean value returned by awaitTermination().

List shutdownNow()

After calling shutdownNow(), we first send interrupt signals to all threads in the thread pool, attempting to interrupt the execution of these tasks. Then we move the tasks in the queue to a List and return them. We can do some more operations based on the List. ) source code as follows:

public List<Runnable> shutdownNow() { List<Runnable> tasks; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); // Convert the running state of the thread advanceRunState(STOP); InterruptWorkers () interruptWorkers() interruptWorkers() interruptWorkers() interruptWorkers() interruptWorkers() interruptWorkers(); Tasks = drainQueue(); // Put the tasks in the queue into the tasks collection and return. } finally { mainLock.unlock(); } tryTerminate(); return tasks; }Copy the code

Principle of thread pool reuse

Principle of thread pool reuse

Thread pooling can decouple threads from tasks, threading by Thread and task by task, getting rid of the previous restriction that one Thread must correspond to one task when creating threads through threads. In the Thread pool, the same Thread can constantly pull new tasks from BlockingQueue to execute. The core principle is that Thread pools encapsulate threads. Thread.start() is not called every time a task is executed. Instead, each thread is asked to execute a “loop task”, in which it constantly checks to see if there are any more tasks waiting to be executed, and if there are, executes the task directly, calling the run method of the task as if it were a normal method. The run() methods for each task are concatenated, so the number of threads does not increase. The execute code is as follows:

Public void execute(Runnable command) {// If (command == null) throw new NullPointerException(); int c = ctl.get(); /** * If the number of threads is smaller than the number of core threads, call addWorker() * to add a Worker. */ if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (! addWorker(command, false)) reject(command); }Copy the code

Let’s first analyze the above part of the code, let’s first analyze the following code:

if (workerCountOf(c) < corePoolSize) {
         if (addWorker(command, true))
             return;
        c = ctl.get();
    }
Copy the code

The addWorK(comond,tue) method is used to create a thread in the thread pool and perform the task passed in as the first argument. Its second argument is a Boolean. If the Boolean value true is passed to indicate whether the current thread is less than corePoolSize, then new threads are added; if the value is greater than or equal to corePoolSize, no threads are added. Similarly, if false is passed to indicate whether the current thread is less than maxPoolSize, if the current thread is less than maxPoolSize, new threads will be added, and if the current thread is greater than or equal to maxPoolSize, no new threads will be added. The addWorker() method succeeds if it returns true and fails if it returns false.

Let’s look at the following part of the code

if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! IsRunning (recheck) && remove(command)) // Execute reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); }Copy the code

If (isRunning(c) &&workqueue.offer (command)) isRunning, the thread pool isRunning. If (isRunning(c) &&workqueue.offer (command)) If the thread pool status is Running, the task is placed in the workqueue.offer (command). If the thread pool is no longer in the Running state, the thread pool is closed, then the task that was just added to the task queue is removed, and the reject policy is implemented.

Next we have the else branch logic for the code above:

else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
Copy the code

If the thread pool status is Running, the thread pool status is Running. If the thread pool status is Running, the thread pool status is Running. If the thread pool status is Running, the thread pool status is Running. That’s workerCountOf**(recheck) == 0, then call addWorker() to create a new thread.

Then let’s look at the last part of the code:

else if (! addWorker(command, false)) reject(command);Copy the code

At this point, the thread pool is not Running or the number of threads is greater than or equal to the number of core threads and the task queue is full, so new threads need to be added until the number of threads reaches the “maximum number of threads”, so the addWorker method is called again and the second argument is passed false, If the number of threads is smaller than maxPoolSize, new threads will be added; if the number is greater than or equal to maxPoolSize, no new worker will be created. If the addWorker method returns true, the task is added successfully. If the addWorker method returns false, the task is added successfully. If the number of threads reaches maxPoolSize, execute the reject policy. If the pool is not Running at this point, addWorker fails and returns false, so the reject policy method is also executed.

Therefore, we need to focus on the analysis of addWorker() method. Worker here can be understood as the packaging of Thread. There is a Thread object inside the Worker, which is the Thread that actually performs the task. Therefore, a Worker corresponds to a thread in the thread pool, and addWorker means adding threads. Let’s look at some of the methods in addWorker:

boolean workerStarted = false; boolean workerAdded = false; //worker is an inner class that implements Runnable and encapsulates Thread worker w = null; Try {// get the firstTask w = new Worker(firstTask); final Thread t = w.thread; if (t ! = null) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { int rs = runStateOf(ctl.get()); if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new  IllegalThreadStateException(); // Collection, which contains all worker threads in the pool. Access is available only when a master lock is held. workers.add(w); int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } if (workerAdded) {// Call the start method of the thread. t.start(); workerStarted = true; } } } finally { if (! workerStarted) addWorkerFailed(w); }Copy the code

We can see from the comments in the above figure that addWork actually calls the start method of the encapsulated thread to start the thread. Let’s continue to see how the run method of the worker internal class is implemented:

public void run() { runWorker(this); }final void runWorker(Worker w) { Thread wt = Thread.currentThread(); // Get the first task to execute, first in first out Runnable task = w.task; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; // The logic that implements thread reuse is mainly in the body of a while loop while (task! = null || (task = getTask()) ! = null) { w.lock(); if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && ! wt.isInterrupted()) wt.interrupt(); try { beforeExecute(wt, task); Throwable thrown = null; Try {// call task's run method directly instead of creating a new thread task.run(); } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); }}Copy the code

From the above figure, we can see that the run() method of the inner class worker actually calls the runWorker(this) method. The logic to achieve thread reuse is mainly carried out in a different loop body while, so the runWorker(this) method mainly does two things:

  1. Get the task to be executed from the workQueue either by getting the Worker’s firstTask or by getting the getTask method.

  2. Call the task’s run method directly to perform a specific task (rather than creating a new thread and calling the thread’s start() method).

conclusion

Well, this article mainly analyzes the basic concepts and core principles of thread pool, is also the author of the summary of all aspects of thread pool learning, basically after reading this article can deal with a lot of thread pool related interview and daily development needs, if there are any deficiencies or mistakes hope readers can give suggestions! Follow me if you like