1.使用线程池的好处和缺点
- 通过重复利用已创建的线程, 减少在创建和销毁线程上所花的时间以及系统资源的开销。
- 提高响应速度。 当任务到达时, 任务可以不需要等到线程创建就可以立即行。
- 提高线程的可管理性。 使用线程池可以对线程进行统一的分配和监控。
- 如果不使用线程池, 有可能造成系统创建大量线程而导致消耗完系统内存。
虽然线程池是构建多线程应用程序的强大机制, 但使用它并不是没有风险的。
- 线程池的大小。 多线程应用并非线程越多越好, 需要根据系统运行的软硬件环境以及应用本身的特点决定线程池的大小。 一般来说, 如果代码结构合理的话,线程数目与CPU数量相适合即可。如果线程运行时可能出现阻塞现象,可相应增加池的大小;如有必要可采用自适应算法来动态调整线程池的大小,以提高CPU的有效利用率和系统的整体性能。
- 并发错误。 多线程应用要特别注意并发错误, 要从逻辑上保证程序的正确性, 注意避免死锁现象的发生。
- 线程泄漏。 这是线程池应用中一个严重的问题, 当任务执行完毕而线程没能返回池中就会发生线程泄漏现象。
2.ThreadPoolExecutor
ThreadPoolExecutor是线程池的真实实现,它的构造方法提供了一系列参数来配置线程池。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
参数 | 说明 |
---|---|
corePoolSize | 线程池维护线程的最少数量 |
maximumPoolSize | 线程池维护线程的最大数量 |
keepAliveTime | 线程池维护线程所允许的空闲时间 |
unit | 线程池维护线程所允许的空闲时间的单位 |
workQueue | 线程池所使用的缓冲队列 |
handler | 线程池对拒绝任务的处理策略 |
当一个任务通过execute(Runnable)方法欲添加到线程池时:
- 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
- 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。
- 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
- 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。
也就是处理任务的优先级为:
- 核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
- 当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。
unit可选的参数为java.util.concurrent.TimeUnit中的几个静态属性:
NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS。
workQueue我常用的是:java.util.concurrent.ArrayBlockingQueue
handler有四个选择:
参数 | 说明 |
---|---|
ThreadPoolExecutor.AbortPolicy() | 抛出java.util.concurrent.RejectedExecutionException异常 |
ThreadPoolExecutor.CallerRunsPolicy() | 重试添加当前的任务,他会自动重复调用execute()方法 |
ThreadPoolExecutor.DiscardOldestPolicy() | 抛弃旧的任务 |
ThreadPoolExecutor.DiscardPolicy() | 抛弃当前的任务 |
使用:
public class TestThreadPool {
private static int produceTaskSleepTime = 2;
private static int produceTaskMaxNumber = 10;
public static void main(String[] args) {
// 构造一个线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),
new ThreadPoolExecutor.DiscardOldestPolicy());
for (int i = 1; i <= produceTaskMaxNumber; i++) {
try {
// 产生一个任务,并将其加入到线程池
String task = "task@ " + i;
threadPool.execute(new ThreadPoolTask(task));
// 便于观察,等待一段时间
Thread.sleep(produceTaskSleepTime);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
/**
* 线程池执行的任务
*/
class ThreadPoolTask implements Runnable, Serializable {
private static final long serialVersionUID = 0;
private static int consumeTaskSleepTime = 2000;
// 保存任务所需要的数据
private Object threadPoolTaskData;
ThreadPoolTask(Object tasks) {
System.out.println("生成子线程计算任务: " + tasks);
this.threadPoolTaskData = tasks;
}
public void run() {
// 处理一个任务,这里的处理方式太简单了,仅仅是一个打印语句
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程计算任务: " + threadPoolTaskData + " 执行完成!");
try {
// //便于观察,等待一段时间
Thread.sleep(consumeTaskSleepTime);
} catch (Exception e) {
e.printStackTrace();
}
threadPoolTaskData = null;
}
}
线程池分类
Executors下定义了几个线程池使用
1. newFixedThreadPool
- 是一种固定的线程池,不会被回收,除非线程池被关闭了。当所有的线程都处于活动状态时,新任务就处于等待状态,知道线程池空闲下来。
- 只有核心线程,没有超市机制。
- 任务队列也没有大小限制。
- 任务队列没有大小限制
- 由于只有核心线程,并且不会被回收,这就意味着它能快速地相应界面请求。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
2. newCachedThreadPool
- 没有核心线程,比较适合执行大量的好事较少的任务
- 缓存型池子,先查看池中有没有以前建立的线程,如果有,就 reuse 如果没有,就建一个新的线程加入池中
- 缓存型池子通常用于执行一些生存期很短的异步型任务。因此在一些面向连接的daemon型SERVER中用得不多。但对于生存期短的异步任务,它是 Executor 的首选。
- 能 reuse 的线程,必须是 timeoutIDLE内的池中线程,缺省timeout是60s,超过这个IDLE时长,线程实例将被终止及移出池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
3. newScheduledThreadPool
- 核心线程数固定的,非核心线程数没有限制,并且当非核心线程闲置的时候回呗立即回收。
- 主要用于执行定时任务和具有固定周期的重复任务
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
4. newSingleThreadExecutor
- 只有一个核心线程,确保所有任务都在同一个线程中按顺序执行。
- 统一所有的外界任务到一个线程中,这使得在这些任务之间不需要处理线程同步的问题。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}