1. Introduction to thread pool
2. Thread pool usage
3. Detailed explanation of thread pool parameters
4. How to set thread pool parameters reasonably
1. Introduction to Thread Pool Let’s first introduce what is a thread pool? In previous posts, when we introduced threads, both Runnable and Calllable, we ended up using new Thread().start(); In a real production environment, we would not use this notation because it has several serious drawbacks: A. Every time new Thread() creates a new object, it incurs overhead and causes the server to perform worse. B. With unlimited new Thread(), we inadvertently create too many threads competing for resources, which may result in frequent context switching, excessive resource usage, deadlocks or OOM. C. Lack of more functions, such as timed execution, periodic execution, thread interrupts.
The Java thread pool is born!
JAVA thread pool to do the main work is to control the number of threads running, process lieutenant general task in the queue and then start the task after the thread creation, if the number of threads than the largest number of execution, beyond the number of threads will wait in line, waiting for the other thread is done, and then removed from the queue tasks to perform.
Its main features: thread consumption, control the maximum number of concurrent, management thread.
First, reduce resource consumption by reusing the threads you create to reduce the cost of thread creation and destruction. Secondly, improve the response speed, when the task arrives, the task can do not need to wait until thread creation can be executed immediately Third: improve the manageability of threads, thread is a scarce resource, if the unlimited access to create, not only consumes system resources, also can reduce the stability of the system, using a thread pool can distribute uniformly, tuning and monitoring.
Having said that, let’s take a look at how to use the Java thread pool.
2. Thread pool use let’s first introduce Java with the creation of thread pool tool class!
Executors
It provides three methods to create a thread pool:
ExecutorService threadPool = Executors.newFixedThreadPool(5); / / a fixed number of thread pool ExecutorService threadPoo2 = Executors. NewCachedThreadPool (); / / a multi-threaded pool ExecutorService threadPoo3 = Executors. NewSingleThreadExecutor (); // One thread per pool
As the name implies: The newFixedThreadPool() method creates a pool with a fixed number of threads. The newCachedThreadPool() method creates a pool with a fixed number of threads depending on your computer and your task. NewSingleThreadExecutor () creates a pool of single-threaded threads that can only execute one task at a time
At the same time, after creating the thread pool, use the thread pool’s own Submit or Excute method, and then use the lambda expression to pass the function body, you can execute the task!
threadPool.submit();
threadPool.execute();
At this point, we can try using a thread pool
ExecutorService threadPool = Executors.newFixedThreadPool(5); // try {for (int I = 1; int I = 1; int I = 1; i < 10000; I++) {threadPool. Submit (() -> {System.out.println(Thread.currentThread().getName() + ")); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); }
We created a pool of five threads and started 10,000 threads to see how it worked.
As you can see, the thread number never went beyond the initial value of 5 that we set.
In this case, we can use a different thread pool. We can use the cache thread pool:
ExecutorService threadPoo1 = Executors.newCachedThreadPool(); // try {for (int I = 1; int I = 1; int I = 1; i < 10000; I++) {ThreadPoo1.submit (() -> {System.out.println(Thread.currentThread().getName() + ")); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPoo1.shutdown(); }
As you can see, the more a thread starts,The pool starts as many threads as needed.
Let’s look at the last thread pool:
ExecutorService threadPoo1 = Executors.newSingleThreadExecutor(); // try {for (int I = 1; int I = 1; int I = 1; i < 10000; I++) {ThreadPoo1.submit (() -> {System.out.println(Thread.currentThread().getName() + ")); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPoo1.shutdown(); }
View the results:
It can be seen that foreverThere’s only one threadIn the run.
Isn’t that amazing? ! Let’s take a look at how the thread pool creation method actually works.
First look at the method for a fixed number of threads:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, // New LinkedBlockingQueue<Runnable>()); // New LinkedBlockingQueue<Runnable>()); // New LinkedBlockingQueue<Runnable>()); // Block queue used}
Cache thread pool method:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
Single thread pool method:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
As you can see from the source code comments I gave you, the maximum number of fixed thread pools and the maximum number of permanent threads in the cache thread pool are both passed in as parameters, and the maximum number of threads in the cache thread pool is Integer.MAX_VALUE The underlying single-thread pool uses synchronousQueue: a queue that does not store an element, i.e. a queue for a single element. LinkedBlockingQueue is used at the bottom of the multi-thread pool: Bounded (but the default value is INTEGER.MAX_VALUE) blocking queues can be seen in this article I wrote earlier: Java Concurrent Programming — An Introduction to Blocking Queues Theory and its APIs.
So how exactly do we use it in production?
The answer is ———— none, since the LinkedBlockingQueue has a maximum length of INTEGER.MAX_VALUE, which is unbounded and may cause the server memory to burst and create OOM. So, let’s learn about the parameters for creating a custom thread pool!
The ThreadPoolExecutor class has several arguments:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
We can see that there are seven parameters: the number of resident core threads in the CorePoolSize thread pool; the maximum number of simultaneous execution threads that the MaximumPoolSize thread pool can hold; This value must be greater than or equal to 1 keppAliveTime the lifetime of the extra idle thread unit workQueue task queue, committed but not yet executed task ThreadFactory represents the threadFactory that spawns the worker thread in the thread pool and is used to create the thread. If the queue is full and the number of worker threads is greater than or equal to the maximum number of threads in the pool, the handler will reject the policy by default.
The first few parameters are easy to understand, but the important thing is that this method can customize the type of the blocking queue so that we can avoid using LinkedBlockingQueue.
When the core thread is full and the blocking queue is full, the next incoming thread will initiate the rejection policy. There are several common rejection policies:
AbortPolicy(default) : Throw a RejectExcutionException directly to prevent the thread from running. CallerRunsPolicy: Returns the extra task to the caller, thereby reducing traffic.
Discard doldestpolicy: Discard the longest-waiting task in a wait queue and then add the current task to the wait queue.
DiscardPolicy: Discards the task directly.
Next, let’s try creating our own thread pool and try to perform some tasks using that thread pool.
ThreadPoolExecutor = new ThreadPoolExecutor(2, 5, 5, 5, 5) TimeUnit.SECONDS,// New ArrayBlockingQueue<Runnable>(10),// Block Queue Executors. DefaultThreadFactory (), / / generates a thread pool worker thread thread factory in new ThreadPoolExecutor. AbortPolicy ()); Try {for (int I = 1; int I = 1; int I = 1; i < 10000; I++) {threadPoolExecutor. Submit (() - > {System. Out. Println (Thread. The currentThread (). The getName () + "is being dealt with business"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPoolExecutor.shutdown(); }}
With the current rejection policy, it is obvious that the number of threads executing exceeds the number of threads in the pool, and an exception is thrown. Let’s look at the result:
We can try a different rejection policy: DiscardPolicy.
ThreadPoolExecutor = new ThreadPoolExecutor(2, 5, 5, 5, 5) TimeUnit.SECONDS,// New ArrayBlockingQueue<Runnable>(10),// Block Queue Executors. DefaultThreadFactory (), / / generates a thread pool worker thread thread factory in new ThreadPoolExecutor. DiscardPolicy ()); // Rejection policy
It runs and does not throw any exceptions.
How to configure the parameters of the thread pool? In fact, there is only one parameter we need to configure most: the number of resident core threads in the thread pool
When our thread pool usage scenario is CPU intensive (most of the tasks of the process depend on the CPU’s computing power to complete), then to prevent the overhead caused by context switching, We generally set the maximum number of threads to equal the number of CPU cores (Runtime.getRuntime().availableProcessors();).
When our thread pool usage scenario is IO intensive (most of the tasks are read in, output data), then the CPU is not very, very busy, most of the waiting cases, the maximum thread can be twice the number of CPU cores.
5. To sum up, we have introduced the principle of thread pool and detailed explanation of parameters. Most importantly, to create a thread pool, you must manually use the constructor method to create, can not use the default method class to create!
Finally, let’s summarize the thread pool execution flow with two diagrams:
Well, that’s all for today’s sharing!