【详解】Executors框架之Executors
这里写目录标题
简介
Java通过Executors提供五种线程池,分别为:
newCachedThreadPool
:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。newFixedThreadPool
:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。newScheduledThreadPool
:创建一个定长线程池,支持定时及周期性任务执行。newSingleThreadExecutor
:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。newWorkStealingPool
:创建一个ForkJoin线程池,线程数是CPU核数,可以充分利用CPU资源
newCachedThreadPool
分析构造方法的参数
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- 初始线程数为:0
- 最大线程数为:int的最大值
- 超时时间为:1分钟
- 阻塞队列采用的是:SynchronousQueue
- 拒绝策略:默认策略(抛出异常)
根据参数可以得到以下结论:
- 这个线程池适用情况是
短任务
情况。 - 采用SynchronousQueue,每当提交一个任务,都会超过阻塞队列的长度,导致创建线程,所以说:每当提交一个任务,都会创建一个线程,可能造成OOM。
- 当线程空闲1分钟就会,销毁,所以该线程池会频繁的创建和销毁线程,最终会线程池会自己销毁
public class ExecutorsTest {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newCachedThreadPool();
System.out.println("当前的线程数为:"+executorService.getActiveCount());
executorService.execute(()-> System.out.println("========="));
IntStream.range(0,100).boxed().forEach(i->executorService.execute(()->{
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ " [ "+ i +" ]");
}));
TimeUnit.SECONDS.sleep(2);
System.out.println("当前的线程数为:"+executorService.getActiveCount());
}
}
结果:
当前的线程数为:0
=========
当前的线程数为:100
当前的线程数为:0
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- 初始线程数为:nThreads
- 最大线程数为:nThreads
- 超时时间为:0毫秒
- 阻塞队列采用的是:LinkedBlockingQueue
- 拒绝策略:默认策略(抛出异常)
根据参数可以得到以下结论:
- 该线程池的线程数是用户自定义的,不会增加,不会减少,线程池不会自己销毁
- 阻塞队列是无限大的,不会执行拒绝策略。
- 可能会堆集无限的请求,导致OOM
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
- 初始线程数为:1
- 最大线程数为:1
- 超时时间为:0毫秒
- 阻塞队列采用的是:LinkedBlockingQueue
- 拒绝策略:默认策略(抛出异常)
根据参数可以得到以下结论:
只有一个线程
的固定线程池- 缺点和固定线程池一样
只有ExecutorService方法
<mark>和一个线程的区别</mark>
newSingleThreadExecutor | Thread |
---|---|
任务执行完成后,不会自动销毁,可以复用 | 任务执行完成后,会自动销毁 |
可以将任务存储在阻塞队列中,逐个执行 | 无法存储任务,只能执行一个任务 |
newWorkStealingPool
public static ExecutorService newWorkStealingPool(int parallelism) {
return new ForkJoinPool
(parallelism,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool
(Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
//Returns the number of processors available to the Java virtual machine.
Runtime.getRuntime().availableProcessors()
分析源码我们可以得知
- 采用的ForkJoin框架,可以将任务进行分割,同时线程之间会互相帮助
- 最大的线程数是CPU核数,充分利用CPU资源
newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
- 创建的是一个定时的任务,每隔一段时间就会运行一次
首先可以对比的就是Timer这个类
public class ExecutorsTest {
public static void main(String[] args) throws InterruptedException {
Timer timer = new Timer();
final TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("=====" + System.currentTimeMillis());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
timer.schedule(task,0,1000);
}
}
结果
=====1589162310908
=====1589162312910
=====1589162314911
=====1589162316911
可以发现:如果任务时间超过了定时时长,就无法按照预定的时间执行
其他工具的解决方式:
crontab
定时处理器为了确保时间的正确性,会重新启一个线程
有三个方法
-
schedule(commod,delay,unit) ,这个方法是说系统启动后,需要等待多久执行,delay是等待时间。只执行一次,没有周期性。
-
scheduleAtFixedRate(commod,initialDelay,period,unit),这个是以period为固定周期时间,按照一定频率来重复执行任务,initialDelay是说系统启动后,需要等待多久才开始执行。例如:如果设置了period为5秒,线程启动之后执行了大于5秒,线程结束之后,立即启动线程的下一次,如果线程启动之后只执行了3秒就结束了那执行下一次,需要等待2秒再执行。这个是优先保证任务执行的频率,
-
scheduleWithFixedDelay(commod,initialDelay,delay,unit),这个是以delay为固定延迟时间,按照一定的等待时间来执行任务,initialDelay意义与上面的相同。例如:设置了delay为5秒,线程启动之后不管执行了多久,结束之后都需要先生5秒,才能执行下一次。这个是优先保证任务执行的间隔。