前言
Java线程池攻略<一>:初识ThreadPoolExecutor
Java线程池攻略<二>:四种常用线程池剖析
基于ThreadPoolExecutor
共有四种类型线程池
FixedThreadPool
:固定数量线程池SingleThreadExecutor
:单线程线程池CachedThreadPool
:缓存线程池ScheduledThreadPoolExecutor
:调度线程池
FixedThreadPool
固定数量线程池:newFixedThreadPool()
//被称为可重用固定线程数的线程
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
示例
public class DemoFixedThreadPool {
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
// 创建需执行的任务
Runnable task = () -> {
LocalDateTime date=LocalDateTime.now();
System.out.println(" 当前时间为:"+ date);
};
// 线程池添加任务
fixedThreadPool.execute(task);
//关闭线程池
fixedThreadPool.shutdown();
try {
fixedThreadPool.awaitTermination(10000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
特性
- 如果当前运行的线程数小于
corePoolSize
, 如果再来新任务的话,就创建新的线程来执行任务; - 当前运行的线程数等于
corePoolSize
后, 如果再来新任务的话,会将任务加入LinkedBlockingQueue
; - 线程池中的线程执行完 手头的任务后,会在循环中反复从 LinkedBlockingQueue 中获取任务来执行
为什么不推荐使用FixedThreadPool
?
FixedThreadPool
使用无界队列 LinkedBlockingQueue
(队列的容量为 Integer.MAX_VALUE)作为线程池的工作队列会对线程池带来如下影响 :
- 当线程池中的线程数达到
corePoolSize
后,新任务将在无界队列中等待,因此线程池中的线程数不会超过corePoolSize
; - 由于使用无界队列时
maximumPoolSize
将是一个无效参数,因为不可能存在任务队列满的情况。所以,
通过创建 FixedThreadPool
的源码可以看出创建的 FixedThreadPool
的 corePoolSize
和 maximumPoolSize
被设置为同一个值。
- 由于 1 和 2,使用无界队列时
keepAliveTime
将是一个无效参数; - 运行中的
FixedThreadPool
(未执行 shutdown()或 shutdownNow())不会拒绝任务,在任务比较多的时候会导致 OOM(内存溢出)。
SingleThreadExecutor
单线程线程池:newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
示例
public class DemoSingleThreadPool {
public static void main(String[] args) {
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 创建需执行的任务
Runnable task = () -> {
LocalDateTime date=LocalDateTime.now();
System.out.println(" 当前时间为:"+ date);
};
// 线程池添加任务
singleThreadExecutor.execute(task);
//关闭线程池
singleThreadExecutor.shutdown();
try {
singleThreadExecutor.awaitTermination(10000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
CachedThreadPool
缓存线程池: newCachedThreadPool()
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
示例
public class DemoCacheThreadPool {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 创建需执行的任务
Runnable task = () -> {
LocalDateTime date=LocalDateTime.now();
System.out.println(" 当前时间为:"+ date);
};
// 线程池添加任务
cachedThreadPool.execute(task);
//关闭线程池
cachedThreadPool.shutdown();
try {
cachedThreadPool.awaitTermination(10000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
特性
CachedThreadPool
的corePoolSize
被设置为空(0),maximumPoolSize
被设置为 Integer.MAX.VALUE
,空闲线程的等待时间为60秒
即它是无界的,这也就意味着如果主线程提交任务的速度高于 maximumPool
中线程处理任务的速度时,
CachedThreadPool
会不断创建新的线程。极端情况下,这样会导致耗尽 cpu 和内存资源
ScheduledThreadPoolExecutor
调度线程池:newScheduledThreadPool()
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
示例
public class DemoScheduleThreadPool {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
// 创建需执行的任务
Runnable task = () -> {
LocalDateTime date=LocalDateTime.now();
System.out.println(" 当前时间为:"+ date);
};
// 线程池添加任务 每4秒执行一次
scheduledExecutorService.scheduleAtFixedRate(task, 5L, 4L, TimeUnit.SECONDS);
//关闭线程池, 不能关闭
//scheduledExecutorService.shutdown();
}
}
特性
线程 1 从 DelayQueue 中获取已到期的 ScheduledFutureTask(DelayQueue.take())。到期任务是指 ScheduledFutureTask的 time 大于等于当前系统的时间;
- 线程 1 执行这个 ScheduledFutureTask;
- 线程 1 修改 ScheduledFutureTask 的 time 变量为下次将要被执行的时间;
- 线程 1 把这个修改 time 之后的 ScheduledFutureTask 放回 DelayQueue 中(DelayQueue.add())。
线程池参数确定
有一个简单并且适用面比较广的公式:
- CPU 密集型任务(N+1): 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
- I/O 密集型任务(2N): 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。
如何判断是 CPU 密集任务还是 IO 密集任务?
CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内存中对大量数据进行排序。但凡涉及到网络读取,文件读取这类都是 IO 密集型,这类任务的特点是 CPU 计算耗费时间相比于等待 IO 操作完成的时间来说很少,大部分时间都花在了等待 IO 操作完成上。