Multithreading is cumbersome, including thread creation, destruction, scheduling, etc., and we do not seem to work in this way to create a new thread, in fact, many frameworks use thread pools at the bottom.
Thread pool is a tool to help us manage threads, it maintains multiple threads, can reduce resource consumption, improve system performance.
And by using thread pools, we developers can better focus on the task code, regardless of how the thread executes, and achieve the decoupling of task submission and execution.
This article will explain the why-why-how of thread pools:
- What is a thread pool
- Why thread pools
- How do I use thread pools
At the same time, there is a list of Java core knowledge points and interview questions from more than 30 companies.
Friends in need can click:This point! This point!, code word: J j
Thread Pool Thread Pool
Thread pooling is a pooling technology, as are database connection pooling, HTTP connection pooling, and so on.
The idea of pooling is to reduce the consumption of each acquisition and termination of resources and improve the utilization of resources.
For example, if it is not convenient to draw water in some remote areas, people will bring water to the pool every period of time, so that they can directly take it when they usually use it.
Similarly, since creating and destroying threads consumes too many system resources, we built a pool to manage threads in a unified manner. Wouldn’t it be a lot easier to take it from the pool and put it back when you don’t need to destroy it?
Thread pools in Java are implemented by the java.util.Concurrent package juc, most notably the ThreadPoolExecutor class. We’ll talk more about how to use it later.
Benefits of thread pools
In the first article on multi-threading, we said that processes apply for resources to be used by threads, so threads are very heavy on system resources, so we can use thread pool to manage threads can solve this resource management problem.
For example, because there is no need to create and destroy threads, I take them every time I need to use them and put them back after I have used them, which saves a lot of resource overhead and improves the running speed of the system.
Unified management and scheduling can reasonably allocate internal resources and adjust the number of threads according to the current situation of the system.
In summary, there are three advantages:
- Reduce resource consumption: Avoid creating and destroying threads multiple times by reusing existing threads to perform tasks.
- Speed up: Since creating a thread is eliminated, tasks can be executed as soon as they are handed to you.
- Additional functionality: The extensibility of the thread pool allows us to add new functionality ourselves, such as timing and latency to execute certain threads.
With that said, let’s look at how to use thread pools
Implementation of thread pools
Java gives us the Executor interface to use thread pools.
There are two common categories of thread pools:
- ThreadPoolExecutor
- ScheduledThreadPoolExecutor
The difference between the two is that the first one is ordinary, and the second one can be executed on a regular basis.
Of course, there are other thread pools, such as ForkJoinPool, introduced in JDK 1.7, that can break large tasks into smaller ones and then unify them.
So what happens when a task is submitted to a thread pool?
Implementation process
Internally, thread pools actually use the producer-consumer model to decouple threads from tasks, allowing thread pools to manage both tasks and threads.
When a task is submitted to the thread pool, it goes through the following process:
- First it checks if the core thread pool is full. The core thread pool is a pool of threads that the thread pool maintains regardless of the number of users. For example, the total capacity of the thread pool can hold up to 100 threads, and we set the core thread pool to 50, so that 50 threads are kept alive regardless of the number of users. This number, of course, depends on specific business requirements.
- The BlockingQueue, known as BlockingQueue, was mentioned in the producer-consumer section.
- Finally, to determine whether the thread pool is full is to determine whether there are already 100 threads, not 50.
- If it is full and therefore cannot continue to create threads, a saturation policy, or rejection policy, is applied. This saturation strategy will be discussed later.
ThreadPoolExecutor
Let’s focus on ThreadPoolExecutor, which is the most common thread pool.Here we can see that there are four constructors in this class, and when we click through, the first three actually call the last one, so we only need to look at the last one.
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
...
}
Copy the code
Let’s take a closer look at these parameters:
- CorePoolSize: This is the size of the core thread pool mentioned above. Threads in the core are never unemployed.
corePoolSize the number of threads to keep in the pool, even if they are idle, unless {@code allowCoreThreadTimeOut} is set
- MaximumPoolSize: specifies the maximum capacity of the thread pool.
maximumPoolSize the maximum number of threads to allow in the pool
- KeepAliveTime: indicates the keepAliveTime. This time refers to how long it takes to destroy idle threads when the number of threads in the thread pool exceeds the number of core threads.
keepAliveTime when the number of threads is greater than the core,this is the maximum time that excess idle threads will wait for new tasks before terminating.
- Unit: Indicates the time unit corresponding to the survival time.
unit the time unit for the {@code keepAliveTime} argument
- WorkQueue: This is a blocking queue. Thread pools are also a kind of producer-consumer model. Tasks – acting as producers, threads – acting as consumers, so this blocking queue is used to coordinate production and consumption.
workQueue the queue to use for holding tasks before they are executed.
- ThreadFactory: This uses engineering mode to create threads.
threadFactory the factory to use when the executor creates a new thread
- Handler: This is the rejection policy.
handler the handler to use when execution is blocked because the thread bounds and queue capacities are reached
So we can construct a thread pool by passing in these seven parameters ourselves, and of course, Java has wrapped up several classes of thread pools that we can easily use.
- newCachedThreadPool
- newFixedThreadPool
- newSingleThreadExecutor
Let’s look at the meaning and usage of each.
newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
Copy the code
And here we can see,
- The core thread pool is zero, meaning it doesn’t hold any threads permanently;
- The maximum capacity is integer.max_value.
- The lifetime of each thread is 60 seconds, meaning that if the thread is not used for 1 minute, it is reclaimed;
- Finally, synchronous queues were used.
Its applicable scenarios are stated in the source code:
These pools will typically improve the performance of programs that execute many short-lived asynchronous tasks.
Here’s how to use it:
Public class newCacheThreadPool {public static void main(String[] args) {// Create a thread pool ExecutorService = Executors.newCachedThreadPool(); For (int I = 0; i < 50; i++) { executorService.execute(new Task()); Executorservice.shutdown (); }}Copy the code
Execution Result:You can clearly see that threads 1, 2, 3, 5, and 6 are being reused very quickly.
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
Copy the code
The characteristics of this thread pool are:
- The number of threads in the thread pool is fixed, which is what we need to pass through when we create the thread pool;
- Threads that exceed this number need to wait in the queue.
It applies to the following scenarios:
Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue.
public class FixedThreadPool { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 200; i++) { executorService.execute(new Task()); } executorService.shutdown(); }}Copy the code
Here I limit the number of threads in the thread pool to 10. Even if there are 200 tasks that need to be executed, only 1 to 10 threads can run.
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
Copy the code
This thread pool, as the name implies, has only one thread in it.
Applicable scenarios are as follows:
Creates an Executor that uses a single worker thread operating off an unbounded queue.
So let’s see what happens.
public class SingleThreadPool { public static void main(String[] args) { ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 100; i++) { executorService.execute(new Task()); } executorService.shutdown(); }}Copy the code
Here I can obviously feel some lag in the result, which was not in the first two examples, after all, there is only one thread running.
summary
So when you use a thread pool, you’re actually calling the ThreadPoolExecutor class with different arguments.
Note two parameters in particular:
- One is the choice of workQueue, and this is the choice of blocking queue, and if you want to write a whole article about it, I’ll write about it later.
- The second is the setting of handler.
We can see that there is no handler in any of the three specific thread pools above because they all use defaultHandler.
/**
* The default rejected execution handler
*/
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
Copy the code
ThreadPoolExecutor implements RejectedExecutionHandler ();
- AbortPolicy rejected tasks and throw an exception RejectedExecutionException. This is what I call a “formal rejection.” For example, if you go through the final round of interviews and get a letter from the HR.
- DiscardPolicy rejects the task but says nothing. This is a silent refusal, for example, when most companies turn down their resumes.
- DiscardOldestPolicy: Discarding an old task and implementing a new one
- CallerRunsPolicy calls the thread directly to handle the task.
So the default strategy for all three thread pools is the first one, fair and square rejection.
Well, that’s all for this article. There’s a lot more to know about thread pools, such as the execute() and submit() methods, and the life cycle of thread pools.
Friends in need can click:This point! This point!, code word: J j.
There are Java core knowledge points + a full set of architect learning materials and video + first-line factory interview gem + resume template can be received + Ali Meituannetease Tencent Xiaomi IQiyi Quick hand bilibilibilii interview questions +Spring source code collection +Java architecture practice ebook.