1 Overview
This article mainly explains the interface and implementation class of the thread pool in Java
, as well as their basic usage, including:
Executor
/Executors
ExecutorService
ThreadPoolExecutor
ScheduledThreadPoolExecutor
2 Two important interfaces: Executor
+ ExecutorService
Executor
is an interface that just defines a simple task submission method:
//Executor
package java.util.concurrent;
public interface Executor {
void execute(Runnable var1);
}
And ExecutorService
is also an interface, inherits Executor
, and provides some more methods for task submission and management, such as stopping the execution of tasks, etc.:
//ExecutorService
package java.util.concurrent;
import java.util.Collection;
import java.util.List;
public interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long var1, TimeUnit var3) throws InterruptedException;
<T> Future<T> submit(Callable<T> var1);
<T> Future<T> submit(Runnable var1, T var2);
Future<?> submit(Runnable var1);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> var1) throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> var1, long var2, TimeUnit var4) throws InterruptedException;
<T> T invokeAny(Collection<? extends Callable<T>> var1) throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> var1, long var2, TimeUnit var4) throws InterruptedException, ExecutionException, TimeoutException;
}
Two important implementations of ExecutorService
will be described in detail below:
ThreadPoolExecutor
ScheduledThreadPoolExecutor
3 ThreadPoolExecutor
This is the so-called thread pool class. Generally speaking, a thread pool has the following characteristics:
- The thread pool has a certain number of worker threads
- The number of threads and the number of tasks will be controlled and managed to a certain extent
- The execution of tasks is done asynchronously
- The thread pool will be responsible for the statistics of the execution tasks
3.1 A simple example
Let's start with a simple example:
public class Main {
public static void main(String[] args) throws Exception {
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
//执行没有返回值的任务
executor.execute(()-> System.out.println(" Execute the runnable task."));
//执行带返回值的任务,用到了Future泛型类
Future<String> future = executor.submit(()->" Execute the callable task and this is the result.");
//通过get()获取任务结果,get()会在任务未完成时一直阻塞
System.out.println(future.get());
//手动关闭线程池
executor.shutdown();
}
}
As you can see from this simple example, the thread pool can execute tasks with and without return values. If you have a return value, you need to use the get()
method to block the acquisition. In addition, you need to manually close the thread pool after running, otherwise JVM
will not exit, because there are a specified number of active threads in the thread pool, and the condition for JVM
to exit normally is that there is no running non-daemon process in the JVM
process.
3.2 Constructor
The source code of the construction method is as follows:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue)
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
Although four constructors are provided, essentially the last constructor is called, which takes 7 parameters, namely:
corePoolSize
: The number of core threads, even when the core threads in the thread pool are not working, the number of core threads will not decrease. The minimum value of this parameter is 0 and less than or equal tomaximumPoolSize
maximumPoolSize
: used to set the maximum number of threads allowed in the thread poolkeepAliveTime
: When the number of threads in the thread pool exceeds the number of core threads and is idle, the thread pool will reclaim some threads to give up system resources. This parameter can be used to set the time after which threads exceeding the number ofcorePoolSize
will be reclaimed. A parameterunit
representing the time unit is used withunit
: used to set the time unit ofkeepAliveTime
workQueure
: used to store tasks that have been submitted to the thread pool but have not been executedthreadFactory
: Factory for creating threads, developers can customizeThreadFactory
to create threadshandler
: Rejection policy, when the task exceeds the boundary of the blocking queue, the thread pool will reject the newly added task, mainly used to set the rejection policy
3.3 Task execution process
After the thread pool is successfully created, the internal running thread will not be created immediately, ThreadPoolExecutor
will use a Lazy
method to create and run. The thread is only created when the execute task method is called for the first time, for example:
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
assert executor.getActiveCount() == 0;
assert executor.getMaximumPoolSize() == 4;
assert executor.getCorePoolSize() == 2;
executor.execute(()-> System.out.println(" Execute the runnable task."));
assert executor.getActiveCount() == 1;
assert executor.getMaximumPoolSize() == 4;
assert executor.getCorePoolSize() == 2;
(Please add the -ea
parameter when running)
Let's take a look at the specific execution process of the task:
- If the number of running threads is less than the number of core threads, create a new thread and execute the task immediately
- If the number of running threads is greater than or equal to the number of core threads, and the task queue is not full, the task will be put into the task queue first, and then the task queue will be polled to obtain the task running after the number of running threads has completed its own task.
- If the task queue is full and the number of running threads is less than the maximum number of threads, the thread pool will create threads to execute the task, and the number of created threads will be less than the maximum number of threads
- If the task queue is full and the number of running threads has reached the maximum number of threads, and there are no idle running threads at the moment, the task rejection policy is executed, depending on
RejectedEcecutionHandler
- If the threads in the thread pool are idle and the idle time reaches the specified time of
keepAliveTime
, the threads will be recycled untilcorePoolSize
core threads are reserved (but the core threads can also be set to be recycled by timeout, and the core thread timeout is not enabled by default)
3.4 Thread Factory
The thread factory ThreadFactory
is an interface:
package java.util.concurrent;
public interface ThreadFactory {
Thread newThread(Runnable var1);
}
Using the thread factory, you can add custom configuration when creating a thread, such as specifying the name, priority, whether it is a daemon thread, etc. For example, the following is a simple implementation of the thread factory:
public class TestThreadFactory implements ThreadFactory {
private final static String PREFIX = "Test thread[";
private final static String SUFFIX = "]";
private final static AtomicInteger THREAD_NUM = new AtomicInteger();
@Override
public Thread newThread(Runnable runnable) {
ThreadGroup group = new ThreadGroup("My pool");
Thread thread = new Thread(group,runnable,PREFIX+THREAD_NUM.getAndIncrement()+SUFFIX);
thread.setPriority(5);
return thread;
}
}
3.5 Denial Policy
By default, ThreadPoolExecutor
provides four deny policies:
DiscardPolicy
: drop policy, drop tasks directlyAbortPolicy
: Terminate policy, throwRejectedExecutionException
DiscardOldestPolicy
: Policy for discarding the oldest task in the queue (strictly speaking, it needs to be selected according to the task queue, because not all queues areFIFO
)CallerRunsPolicy
: The caller thread execution strategy, the task will block execution in the current thread
Of course, if it cannot meet the needs, you can implement RejectedExecutionHandler
interface to customize the strategy:
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable var1, ThreadPoolExecutor var2);
}
3.6 Close the thread pool
If you do not need the thread pool, you need to manually close the thread pool. The thread pool provides the following three methods:
- Orderly shutdown:
shutdown()
- Close now:
shutdownNow()
- Combo Close:
shutdown()+shutdownNow()
3.6.1 Orderly shutdown
shutdown()
provides an orderly shutdown method to close the thread pool. After calling this method, it will wait for all currently executing tasks to complete and then close, and new submitted tasks will be rejected. Note that this method is non-blocking and returns immediately. If you need to check the shutdown status, you can use:
isShutdown()
: Returns the result of whethershutdown()
was calledisTerminating()
: return whether it is endingisTerminated()
: return whether it has ended
3.6.2 Immediate shutdown
shutdownNow()
method first modifies the thread pool state to shutdown
state, then suspends the unexecuted task, then tries to interrupt the running thread, and finally returns the unexecuted task:
public static void main(String[] args) throws Exception {
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), new TestThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
IntStream.range(0,10).forEach(i-> executor.execute(()-> {
try{
TimeUnit.SECONDS.sleep(5);
}catch (Exception e){
e.printStackTrace();
}
}));
List<Runnable> runnables = executor.shutdownNow();
System.out.println(runnables.size());
}
output:
8
BUILD SUCCESSFUL in 326ms
2 actionable tasks: 2 executed
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at java.base/java.lang.Thread.sleep(Thread.java:339)
at java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)
at com.company.Main.lambda$main$0(Main.java:29)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at java.base/java.lang.Thread.sleep(Thread.java:339)
at java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)
at com.company.Main.lambda$main$0(Main.java:29)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
3:14:36 AM: Task execution finished 'Main.main()'.
3.6.3 Combined shutdown
In order to ensure the safe shutdown of the thread pool, a combination of shutdown is generally used to ensure that the running tasks are executed normally and at the same time, the success rate of the thread pool being closed can be improved. Examples are as follows:
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), new TestThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
IntStream.range(0,10).forEach(i-> executor.execute(()-> {
try{
TimeUnit.SECONDS.sleep(5);
}catch (Exception e){
e.printStackTrace();
}
}));
//首先调用shutdown()尝试关闭
executor.shutdown();
try{
//如果等待一段时间后还没关闭
if(!executor.awaitTermination(10,TimeUnit.SECONDS)){
//强制关闭
executor.shutdownNow();
//如果强制关闭失败,比如运行的线程异常耗时且不能被中断
if(!executor.awaitTermination(10,TimeUnit.SECONDS)){
//其他处理,这里只是输出中断失败的信息
System.out.println("Terminate failed.");
}
}
}catch (InterruptedException e){
//如果当前线程被中断,并且捕获了异常,执行立即关闭方法
executor.shutdownNow();
//重新抛出中断信号
Thread.currentThread().interrupt();
}
4 ScheduledThreadPoolExecutor
ScheduledExecutorService
inherits ExecutorService
and provides the feature that tasks are executed regularly. You can use ScheduledThreadPoolExecutor
to implement some special tasks. Of course, there are many methods or frameworks to implement fixed tasks, such as the native shell
implementation, the old-fashioned Timer/TimerTask
implementation, or the specialized framework Quartz
implementation. What I want to talk about here is the internal implementation of JDK
ScheduledThreadPoolExecutor
.
ScheduledThreadPoolExecutor
inherits ThreadPoolExecutor
. In addition to all the methods of ThreadPoolExecutor
, it also defines 4 methods related to schedule
:
<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)
: a method ofone-shot
(execute only once), the task (callable
) will be executed after unit time (delay
), and returnScheduledFuture
immediatelyScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
: is also aone-shot
method, the task will be executed after a unit of time, and the difference from the first method is that the returnedScheduledFuture
does not contain any execution results, but the returnedScheduledFuture
can be used to determine whether the task is completed or not.ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
: The task will be executed continuously afterinitialDelay
according to a fixed rateScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
: The task will execute the task with a fixed delay unit time
The difference between the latter two is as follows:
public static void main(String[] args) throws Exception {
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2);
Runnable runnable = ()->{
long startTimestamp = System.currentTimeMillis();
System.out.println("current timestamp: "+startTimestamp);
try{
TimeUnit.MILLISECONDS.sleep(current().nextInt(100));
}catch (Exception e){
e.printStackTrace();
}
System.out.println("elapsed time: "+(System.currentTimeMillis() - startTimestamp));
};
executor.scheduleAtFixedRate(runnable,10,1000,TimeUnit.MILLISECONDS);
// executor.scheduleWithFixedDelay(runnable,10,1000,TimeUnit.MILLISECONDS);
}
output:
current timestamp: 1619351675438
elapsed time: 97
current timestamp: 1619351676438
elapsed time: 85
current timestamp: 1619351677438
elapsed time: 1
current timestamp: 1619351678438
elapsed time: 1
current timestamp: 1619351679438
elapsed time: 68
current timestamp: 1619351680438
elapsed time: 99
You can see that the tasks always run at a fixed rate, and the start times of each run are always 1000ms
apart.
And using FixedDelay
the output is as follows:
current timestamp: 1619351754890
elapsed time: 53
current timestamp: 1619351755944
elapsed time: 30
current timestamp: 1619351756974
elapsed time: 13
current timestamp: 1619351757987
elapsed time: 80
current timestamp: 1619351759068
elapsed time: 94
current timestamp: 1619351760162
elapsed time: 29
Each start time is the time since the last execution completed plus the time interval ( 1000ms
).
5 Thread pool in Executors
Executors
class provides six static methods for creating thread pools:
FixedThreadPool
SingleThreadExecutor
CachedThreadPool
ScheduledThreadPool
SingleThreadScheduledExecutor
WorkStealingPool
Let's take a look at them separately.
5.1 FixedThreadPool
The source code is as follows:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), threadFactory);
}
The bottom layer of FixedThreadPool
calls ThreadPoolExecutor
, the number of core threads created by default is equal to the maximum number of threads, and the task queue is LinkedBlockingQueue
without boundaries.
5.2 SingleThreadExecutor
The relevant source code is as follows:
public static ExecutorService newSingleThreadExecutor() {
return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), threadFactory));
}
private static class FinalizableDelegatedExecutorService extends Executors.DelegatedExecutorService {
FinalizableDelegatedExecutorService(ExecutorService executor) {
super(executor);
}
protected void finalize() {
super.shutdown();
}
}
It can be seen that SingleThreadPool
is actually a wrapper of the inner class FinalizableDelegatedExecutorService
, the core thread and the maximum number of threads are both 1, and the task queue is an LinkedBlockingQueue
. When GC
occurs, the shutdown()
method is called.
5.3 CachedThreadPool
The source code is as follows:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue(), threadFactory);
}
CachedThreadPool
will create new threads as needed, which are usually used to execute large and short asynchronous tasks. Threads that are not used and idle for more than 60s
are recycled.
5.4 ScheduledThreadPool
The source code is as follows:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
Create the specified number of ScheduledThreadPoolExecutor
.
5.5 SingleThreadScheduledExecutor
The source code is as follows:
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new Executors.DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutor(1));
}
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
return new Executors.DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutor(1, threadFactory));
}
private static class DelegatedScheduledExecutorService extends Executors.DelegatedExecutorService implements ScheduledExecutorService {
private final ScheduledExecutorService e;
DelegatedScheduledExecutorService(ScheduledExecutorService executor) {
super(executor);
this.e = executor;
}
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
return this.e.schedule(command, delay, unit);
}
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
return this.e.schedule(callable, delay, unit);
}
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
return this.e.scheduleAtFixedRate(command, initialDelay, period, unit);
}
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
return this.e.scheduleWithFixedDelay(command, initialDelay, delay, unit);
}
}
In fact, it is SingelThreadPool
+ ScheduledThreadPool
.
5.6 WorkStealingPool
The source code is as follows:
public static ExecutorService newWorkStealingPool(int parallelism) {
return new ForkJoinPool(parallelism, ForkJoinPool.defaultForkJoinWorkerThreadFactory, (UncaughtExceptionHandler)null, true);
}
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool(Runtime.getRuntime().availableProcessors(), ForkJoinPool.defaultForkJoinWorkerThreadFactory, (UncaughtExceptionHandler)null, true);
}
WorkStealingPool
is a thread pool introduced by JDK8
, and it returns ForkJoinPool
. In WorkStealingPool
, if the execution of tasks processed by each thread is time-consuming, the tasks it is responsible for will be "stealed" by other threads, thereby improving the efficiency of concurrent processing.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。