Why do we need several parameters to construct a thread pool? How to avoid OOM in the thread pool? What is the difference between Runnable
and Callable
? This article will answer these questions one by one, and will also give common scenarios and code snippets using thread pools.
Basic knowledge
Executors create thread pool
Creating a thread pool in Java is very simple, you only need to call Executors
the corresponding convenience methods, for example Executors.newFixedThreadPool(int nThreads)
, but convenience not only hides the complexity, but also bury potential hidden dangers (OOM, thread exhaustion) for us.
Executors
List of convenient methods to create a thread pool:
There is nothing wrong with using these shortcuts for small programs. For programs that need to be run for a long time on the server side, create a thread pool that should be used directly ThreadPoolExecutor
. That's right, Executors
the thread pool created by the above method is ThreadPoolExecutor
.
ThreadPoolExecutor construction method
Executors
The shortcut method of creating a thread pool in is actually the called ThreadPoolExecutor
construction method (which is used by timed tasks ScheduledThreadPoolExecutor
). The parameter list of this type of construction method is as follows:
// The complete constructor of the Java thread pool public ThreadPoolExecutor( int corePoolSize, // The number of threads that the thread pool maintains for a long time, even if the thread is in the Idle state, it will not be recycled. int maximumPoolSize, // The upper limit of the number of threads long keepAliveTime, TimeUnit unit, // The idle duration of threads exceeding corePoolSize, // After this time, the excess threads will be recycled. BlockingQueue<Runnable> workQueue, // Queue queue of tasks ThreadFactory threadFactory, // How to generate new threads RejectedExecutionHandler handler) // Rejection strategy
There are 7 parameters, so helpless, so many parameters are really needed to construct a thread pool. These parameters are, the more likely to cause problems are corePoolSize
, maximumPoolSize
, workQueue
and handler
:
corePoolSize
AndmaximumPoolSize
improper settings will affect efficiency and even exhaust threads;workQueue
Improper setting can easily lead to OOM;handler
Improper setting will cause an exception to be thrown when submitting the task.
The correct parameter setting method will be given below.
Working order of thread pool
If fewer than corePoolSize threads are running, the Executor always prefers adding a new thread rather than queuing.
If corePoolSize or more threads are running, the Executor always prefers queuing a request rather than adding a new thread.
If a request cannot be queued, a new thread is created unless this would exceed maximumPoolSize, in which case, the task will be rejected.
corePoolSize -> task queue -> maximumPoolSize -> rejection strategy
Runnable和Callable
There are two types of tasks that can be submitted to the thread pool: Runnable
and Callable
, the difference between the two is as follows:
- Different method signature,
void Runnable.run()
,V Callable.call() throws Exception
- Whether to allow return value,
Callable
allow return value - Whether to allow exceptions to
Callable
be thrown , allow exceptions to be thrown.
Callable
It is an interface added in JDK1.5. As Runnable
a supplement, it allows return values and exceptions to be thrown.
Three ways to submit tasks:
How to use thread pool correctly
Avoid using *** queue
Do not use the Executors.newXXXThreadPool()
shortcut method to create the thread pool, because this method will use the task queue of ***, in order to avoid OOM, we should use ThreadPoolExecutor
the construction method to manually specify the maximum length of the queue:
ExecutorService executorService = new ThreadPoolExecutor(2, 2, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(512), // Use a bounded queue to avoid OOM new ThreadPoolExecutor.DiscardPolicy());
Behavior when explicitly rejecting the task
When the task queue is always full, what submit()
happens when a new task is submitted? RejectedExecutionHandler
The interface provides us with a control method, and the interface is defined as follows:
public interface RejectedExecutionHandler { void rejectedExecution(Runnable r, ThreadPoolExecutor executor);}
The thread pool provides us with several common rejection strategies:
The default rejection behavior of the thread pool AbortPolicy
is to throw RejectedExecutionHandler
an exception, which is an unchecked exception and it is easy to forget to catch it. If you don't care about the event that the task is rejected, you can set the rejection policy to DiscardPolicy
this so that redundant tasks will be silently ignored.
ExecutorService executorService = new ThreadPoolExecutor(2, 2, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(512), new ThreadPoolExecutor.DiscardPolicy());// Specify rejection policy
Get processing results and exceptions
The processing results of the thread pool and the exceptions during the processing are packaged Future
in and obtained when the Future.get()
method is called . The exceptions during the execution will be packaged ExecutionException
. The submit()
method itself will not transmit the results and exceptions during the task execution. The code to get the execution result can be written like this:
ExecutorService executorService = Executors.newFixedThreadPool(4);Future<Object> future = executorService.submit(new Callable<Object>() { @Override public Object call() throws Exception { throw new RuntimeException("exception in call~");// The exception will be passed to the caller when Future.get() is called} }); try { Object result = future.get();} catch (InterruptedException e) { // interrupt} catch (ExecutionException e) { // exception in Callable.call() e.printStackTrace();}
The output of the above code is similar to the following:
Common scenarios for thread pools
Correctly construct the thread pool
int poolSize = Runtime.getRuntime().availableProcessors() * 2;BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(512);RejectedExecutionHandler policy = new ThreadPoolExecutor.DiscardPolicy();executorService = new ThreadPoolExecutor(poolSize, poolSize, 0, TimeUnit.SECONDS, queue, policy);
Get a single result
After submit()
submitting a task to the thread pool, one will be returned Future
. The calling V Future.get()
method can block waiting for the execution result, and the V get(long timeout, TimeUnit unit)
method can specify the timeout period for waiting.
Get multiple results
If multiple tasks are submitted to the thread pool, to get the execution results of these tasks, you can call Future.get()
get in turn . But for this scenario, we should use ExecutorCompletionService . The take()
method of this class always blocks waiting for a certain task to complete, and then returns the Future
object of the task . CompletionService
After submitting tasks to the batch, you only need to call the same number of CompletionService.take()
methods to obtain the execution results of all tasks. The order of obtaining is arbitrary, depending on the order of completion of the tasks:
void solve(Executor executor, Collection<Callable<Result>> solvers) throws InterruptedException, ExecutionException { CompletionService<Result> ecs = new ExecutorCompletionService<Result>(executor);// 构造器 for (Callable<Result> s: solvers)// Submit all tasks ecs.submit(s); int n = solvers.size(); for (int i = 0; i <n; ++i) {// Get every completed task Result r = ecs.take().get(); if (r != null) use(r); }}
Timeout of a single task
V Future.get(long timeout, TimeUnit unit)
The method can specify the timeout time to wait, and the timeout will be thrown if it is not completed TimeoutException
.
Timeout for multiple tasks
Wait for multiple tasks to complete, and set the maximum waiting time, which can be done through CountDownLatch :
public void testLatch(ExecutorService executorService, List<Runnable> tasks) throws InterruptedException{ CountDownLatch latch = new CountDownLatch(tasks.size()); for(Runnable r : tasks){ executorService.submit(new Runnable() { @Override public void run() { try{ r.run(); }finally { latch.countDown();// countDown } } }); } latch.await(10, TimeUnit.SECONDS); // Specify the timeout period}
Thread pool and decoration company
Take the analogy of operating a decoration company. The company waits for customers to submit decoration requests at the office; the company has a fixed number of formal workers to maintain operation; when there are more business in peak seasons, new customer requests will be scheduled, for example, after receiving an order, tell the user to start decoration after one month; When there are too many schedules, in order to avoid users waiting too long, the company will hire some temporary workers through certain channels (such as the talent market, acquaintance introductions, etc.) (note that the recruitment of temporary workers is after the expiration of the schedule); if temporary workers It is too busy, the company will decide not to accept new customers, and directly reject the order.
The thread pool is the "decoration company" in the program, doing all sorts of dirty work. The above process corresponds to the thread pool:
// The complete constructor of the Java thread pool public ThreadPoolExecutor( int corePoolSize, // The number of formal workers int maximumPoolSize, // The maximum number of workers, including regular and temporary workers long keepAliveTime, TimeUnit unit, // The longest time that temporary workers are idle, and will be fired if this time is exceeded. BlockingQueue<Runnable> workQueue , // ThreadFactory threadFactory, // hiring channel RejectedExecutionHandler handler) // Rejection method
to sum up
Executors
Provides us with a convenient way to construct a thread pool. For server programs, we should put an end to these convenient methods, but directly use the thread pool ThreadPoolExecutor
construction method to avoid OOM that may be caused by the *** queue and threads caused by improper limit on the number of threads Problems such as running out of numbers. ExecutorCompletionService
Provides an effective way to wait for the completion of all tasks. If you want to set the waiting timeout time, you can CountDownLatch
complete it.
Refer to
ThreadPoolExecutor API Doc
Author: CarpenterLee
description link: the Java thread pool Detailed - CarpenterLee - blog Park,
the original source: blog Garden
invasion deleted