Why use thread pools?
Here is the code to create a thread and run it:
for (int i = 0; i < 100; i++) { new Thread(() -> { System.out.println("run thread->" + Thread.currentThread().getName()); userService.updateUser(....) ; }).start(); }Copy the code
We want to do asynchrony in this way, or improve performance, and then put some time-consuming operations into a new thread to run.
That’s fine, but there are problems with this code. What are they? Let’s look at some of the problems;
- Create destroy thread resource consumption; We use threads for efficiency purposes, but creating them takes extra time, resources, and system resources for thread destruction.
- CPU resources were limited, and the above code was created with too many threads, resulting in some tasks that could not be completed immediately and the response time was too long.
- Threads can’t be managed, and creating threads without restraint seems like a “pyrgenic” effect on limited resources.
Since we had a problem with manually creating threads above, is there a solution?
Answer: Yes, use thread pools.
Introduction to thread pools
Thread Pool: A technique in which one or more threads are scheduled and reused in a uniform manner, avoiding the overhead of too many threads.
What are the advantages of thread pools?
- Reduce resource consumption. Reduce the cost of thread creation and destruction by reusing created threads.
- Improve response speed. When a task arrives, it can be executed immediately without waiting for the thread to be created.
- Improve thread manageability.
Thread pool usage
In the JDK, you can create a thread pool by using JUC(java.util.concurrent) in either of the following ways: ThreadPoolExecutor and Executors. Executors can create six different thread pool types.
The use of ThreadPoolExecutor
The thread pool uses the following code:
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ThreadPoolDemo { private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue(100)); public static void main(String[] args) { threadPoolExecutor.execute(new Runnable() { @Override public void run() { System.out.println(" Hello, Mr. Tien "); }}); }}Copy the code
The execution results of the above procedures are as follows:
How do you do, Mr. Tian
Core Parameters
ThreadPoolExecutor has four constructors:
You can see that the last constructor has seven constructor parameters, but the first three constructors just wrap the last one, and the first three constructors all end up calling the last one, so we’ll talk about the last one here.
Parameter interpretation
corePoolSize
The number of core threads in the thread pool. By default, core threads always live in the thread pool. If allowCoreThreadTimeOut of ThreadPoolExecutor is set to true, If the thread pool remains idle for longer than keepAliveTime, the core thread is terminated.
maximumPoolSize
Maximum number of threads, the maximum number of threads that can be created when there are not enough threads.
keepAliveTime
The idle timeout of the thread pool is valid by default for non-core threads, which are reclaimed if they are idle longer than this. If allowCoreThreadTimeOut of ThreadPoolExecutor is set to true, core threads will also be reclaimed if they exceed their idle time.
unit
This parameter is used with keepAliveTime to identify the unit of keepAliveTime.
workQueue
The task queue in the thread pool where tasks submitted using the execute() or submit() methods are stored.
threadFactory
Provide thread factories for thread pools to create new threads.
rejectedExecutionHandler
RejectedExecutionHandler is an interface that contains only one rejectedExecution method, which can be used to add event handling for tasks exceeding the maximum number. ThreadPoolExecutor also provides four default rejection policies:
- DiscardPolicy() : Discards the task without processing it.
- DiscardOldestPolicy() : Discards the most recent task in the queue and executes the current task.
- AbortPolicy () : direct selling RejectedExecutionException anomalies (default).
- CallerRunsPolicy() : Executes the task directly using the main thread without dropping the task or throwing an exception.
Use cases with all parameters:
public class ThreadPoolExecutorTest { public static void main(String[] args) throws InterruptedException, ExecutionException { ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(2), new MyThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); threadPool.allowCoreThreadTimeOut(true); for (int i = 0; i < 10; i++) { threadPool.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }}}); } } } class MyThreadFactory implements ThreadFactory { private AtomicInteger count = new AtomicInteger(0); @Override public Thread newThread(Runnable r) { Thread t = new Thread(r); String threadName = "MyThread" + count.addAndGet(1); t.setName(threadName); return t; }}Copy the code
Run output:
main
MyThread1
main
MyThread1
MyThread1
....
Copy the code
This is just to demonstrate all parameter customizations and is not intended for other purposes.
Use of execute() and submit()
Execute () and submit() are used to execute a thread pool. The difference is that the submit() method can receive the return value from the thread pool execution.
The specific use and differences of the two methods are as follows:
ThreadPoolExecutor ThreadPoolExecutor = new ThreadPoolExecutor(2, 10, 10L, timeunit.seconds, new LinkedBlockingQueue(100)); / / use the execute threadPoolExecutor. Execute (new Runnable () {@ Override public void the run () {System. Out. Println (" hello Mr. Tian "); }}); / / submit a Future < String > Future = threadPoolExecutor. Submit (new Callable < String > () {@ Override public String call () Throws Exception {system.out.println (" Hello, Mr. Tian "); Return "return "; }}); System.out.println(future.get());Copy the code
The execution results of the above procedures are as follows:
Hello Mr. Tian hello return valueCopy the code
Executors
Most thread pools are basically encapsulated on the ThreadPoolExecutor constructor. Create thread pools for special scenarios. Executors can be likened to a factory. Create 6 different thread pool types by Following Executors.
Here is a brief explanation of the six methods:
newFixedThreadPool
Create a pool of threads with a fixed number of tasks waiting in the queue for free threads, which can be used to control the maximum number of concurrent applications.
newCacheThreadPool
A thread pool that processes a large amount of work in a short period of time will generate a corresponding thread based on the number of tasks, and will attempt to cache the thread for reuse, or remove the cache if the limit is 60 seconds. If no existing thread is available, a new thread is created and added to the pool, and if there are used threads that have not been destroyed, the thread is reused. Terminates and removes threads from the cache that have not been used for 60 seconds. Therefore, a thread pool that remains idle for a long time does not use any resources.
newScheduledThreadPool
Create a fixed number of thread pools to support the execution of scheduled or periodic tasks.
newWorkStealingPool
Java 8 added a method to create a thread pool. If no parameter is set, the number of threads is used as the number of cpus on the current machine. The thread pool processes tasks in parallel and the execution sequence is not guaranteed.
newSingleThreadExecutor
Create a single thread pool. This thread pool has only one thread working, which is equivalent to a single thread executing all tasks in serial. If the unique thread terminates due to an exception, a new thread will replace it. This thread pool ensures that all tasks are executed in the order in which they were submitted.
newSingleThreadScheduledExecutor
This thread pool is the single-threaded newScheduledThreadPool.
How do thread pools close?
To close the thread pool, you can use the shutdown() or shutdownNow() methods. The difference is:
- Shutdown () : The thread pool is not terminated immediately, but only after all tasks in the task queue have been executed. After the shutdown method is executed, the thread pool will no longer accept new tasks.
- ShutdownNow () : Executing this method immediately changes the thread pool state to STOP and attempts to STOP all executing threads from processing tasks that are still waiting in the pool queue. Executing this method returns unexecuted tasks.
After shutdown(), add tasks to the thread pool with code like this:
import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; public class ThreadPoolExecutorAllArgsTest { public static void main(String[] args) throws InterruptedException, ThreadPoolExecutor ThreadPoolExecutor = new ThreadPoolExecutor(1, 1, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(2), new MyThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); threadPoolExecutor.allowCoreThreadTimeOut(true); / / submitting threadPoolExecutor. Execute (() - > {for (int I = 0; i < 3; I ++) {system.out.println (" submit task "+ I); try { Thread.sleep(3000); } catch (InterruptedException e) { System.out.println(e.getMessage()); }}}); threadPoolExecutor.shutdown(); / / again mention task threadPoolExecutor. Execute (() - > {System. Out. Println (" I want to mention again task "); }); }}Copy the code
The execution results of the above procedures are as follows:
Submit task 0 Submit task 1 Submit task 2Copy the code
As you can see, after shutdown(), no new tasks are accepted, but the previous tasks are executed.
The interview questions
Interview question 1: What are the common methods of ThreadPoolExecutor?
ThreadPoolExecutor has the following common methods:
- Submit ()/execute() : Executes the thread pool
- Shutdown ()/shutdownNow() : Terminates the thread pool
- IsShutdown () : checks whether the thread is terminated
- GetActiveCount () : Number of running threads
- GetCorePoolSize () : Gets the number of core threads
- GetMaximumPoolSize () : Gets the maximum number of threads
- GetQueue () : Gets the task queue in the thread pool
- AllowCoreThreadTimeOut (Boolean) : Sets whether to reclaim core threads when idle
These methods can be used to terminate thread pools, thread pool monitoring, and so on.
Question 2: What is the difference between submit and execute?
Submit () and execute() are used to execute a thread pool, except that execute() cannot have a return method, whereas submit() can receive the return value of a thread pool execution using the Future.
Talk about the meaning of the core parameters required to create a thread pool
ThreadPoolExecutor can contain a maximum of seven parameters:
- CorePoolSize: Number of core threads in the thread pool
- MaximumPoolSize: specifies the maximum number of threads in the thread pool
- KeepAliveTime: Indicates the idle timeout period
- Unit: keepAliveTime Timeout unit (hour, minute, second, etc.)
- WorkQueue: Queue of tasks in a thread pool
- ThreadFactory: provides a threadFactory for the thread pool to create new threads
- RejectedExecutionHandler: rejectedExecutionHandler: Specifies the policy to reject the number of tasks in the thread pool when the number exceeds the maximum
Interview question 3: What is the difference between shutdownNow() and shutdown()?
ShutdownNow () and shutdown() are both used to terminate the thread pool. The difference is that shutdown() does not report an error or immediately terminate the thread. It waits for the cache task in the thread pool to complete before exiting. No new tasks can be added to the thread pool after shutdown(); ShutdownNow () will try to immediately stop the task, if the thread pool and caching task being performed, will throw out the Java. Lang. InterruptedException: sleep interrupted anomalies.
Interview Question 6: Do you know how thread pools work?
When a task needs to be executed in the thread pool, the thread pool will determine that if the number of threads does not exceed the number of core threads, it will create a new thread pool for task execution. If the number of threads in the thread pool exceeds the number of core threads, the task will be put into the task queue for execution. If the task queue exceeds the maximum number of queues and the thread pool does not reach the maximum number of threads, a new thread is created to execute the task. If the maximum number of threads is exceeded, a denial execution policy is executed.
Interview question 5: How to set the number of core threads in the thread pool?
CPU intensive tasks: Such as encryption and decryption, compression, and computing, which require a lot of CPU resources, are mostly PURE CPU computing. Use as small a thread pool as possible, typically number of CPU cores +1. Cpu-intensive tasks cause high CPU usage. If too many threads are enabled, excessive CPU switchover may occur.
IO intensive tasks: such as MySQL database, file reading and writing, and network communication. These tasks do not consume CPU resources, but I/O operations are time-consuming. You can use a slightly larger thread pool, typically 2*CPU cores. IO intensive tasks have low CPU usage. Therefore, you can make full use of CPU time by allowing other threads to handle other tasks while waiting for I/OS.
In addition: the higher the proportion of average working time of threads, the fewer threads are required; The higher the average waiting time ratio of threads, the more threads are needed;
The above values are only theoretical. In actual projects, it is recommended to tune them locally or in the test environment several times to find a relatively ideal value.
Interview question 7: Why do thread pools need to use (blocking) queues?
There are three main points:
- If the thread is created without limit, it may take up too much memory and generate OOM, and it may cause excessive CPU switching.
- The cost of creating a thread pool is high.
Interview question 8: Why do thread pools use blocking queues instead of non-blocking queues?
The blocking queue can ensure that the thread that obtains the task is blocked when there is no task in the task queue, so that the thread enters the wait state and releases CPU resources.
When there is a task in the queue, the corresponding thread is awakened to fetch the message from the queue for execution.
So that threads do not occupy CPU resources all the time.
(The thread executes the task through a loop to retrieve the task from the task queue again for execution, as shown in the following code snippet
while (task ! = null || (task = getTask()) ! = null) {}).Copy the code
You can do it without blocking the queue, but it’s a bit of a hassle to implement. Why not use it if it works?
Interview question 9: Do you know thread pool status?
By getting the thread pool state, you can determine if the thread pool is running, add new tasks, gracefully close the thread pool, and so on.
- RUNNING: Initialization state of the thread pool. Tasks to be executed can be added.
- SHUTDOWN: The thread pool is in the state to be closed. No new tasks are received and only received tasks are processed.
- STOP: The thread pool is shut down immediately, no new tasks are received, tasks in the cache queue are abandoned and ongoing tasks are interrupted.
- TIDYING: Thread pool defends state autonomously, using the terminated() method.
- TERMINATED: TERMINATED state of the thread pool.
Interview question 10: Do you know how thread reuse works in thread pools?
Thread pools decouple threads from tasks, allowing threads to be threads and tasks to be tasks, freeing them from 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 fetch new tasks from the blocking queue to execute. The core principle is that the Thread pool encapsulates the Thread. Instead of calling thread.start () to create a new Thread every time a task is executed, each Thread is told to execute a “loop task”. This “loop task” constantly checks if any task needs to be executed, and if so, executes it directly, that is, calls the run method in the task and executes it as a normal method, by which all the task’s run methods are concatenated using only a fixed thread.
conclusion
This article introduces the disadvantages of not using thread pool *, Executors introduction, six methods of Executors, how to use thread pool, understand the principle of thread pool, core parameters, and 10 to thread pool interview questions.
“Success is not in the future, but from the moment you decide to do, continue to accumulate.