线程池
线程池简介
线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后在需要执行新的任务时重用这些线程而不是新建一个线程。线程池中线程的数量通常完全取决于可用内存数量和应用程序的需求。然而,增加可用线程数量是可能的。线程池中的每个线程都有被分配一个任务,一旦任务已经完成了,线程回到池子中并等待下一次分配任务。
线程池作用
基于以下几个原因在多线程应用程序中使用线程是必须的:
- 线程池改进了一个应用程序的响应时间。由于线程池中的线程已经准备好且等待被分配任务,应用程序可以直接拿来使用而不用新建一个线程。
- 线程池节省了CLR 为每个短生存周期任务创建一个完整的线程的开销并可以在任务完成后回收资源。
- 线程池根据当前在系统中运行的进程来优化线程时间片。
- 线程池允许我们开启多个任务而不用为每个线程设置属性。
- 线程池允许我们为正在执行的任务的程序参数传递一个包含状态信息的对象引用。
- 线程池可以用来解决处理一个特定请求最大线程数量限制问题。
线程池四种创建方式
- newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。示例代码如下:
//返回的ExecutorService是Java用于管理线程池的类:用于控制线程数量和重用线程
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
// try {
// Thread.sleep(index * 1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
cachedThreadPool.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + "---" + index);
}
});
}
总结: 线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
- newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。示例代码如下:
// 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
final ExecutorService newCachedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int index = i;
newCachedThreadPool.execute(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println("i:" + index);
}
});
}
总结:因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。
定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()
- newScheduledThreadPool
// 创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下:
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
newScheduledThreadPool.schedule(new Runnable() {
public void run() {
System.out.println("delay 3 seconds");
}
}, 3, TimeUnit.SECONDS);
- newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
newSingleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("index:" + index);
try {
Thread.sleep(200);
} catch (Exception e) {
// TODO: handle exception
}
}
});
}
线程池图解
Executor:顶层接口,执行线程的工具
ExecutorService:线程池接口
Executors:线程池的静态工厂,提供快捷创建线程池的静态方法
AbstractExecutorService:抽象类,实现了ExecutorService大部分方法
关于线程池日常使用
阿里巴巴java开发手册中明确提出,
线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式
Executors 返回的线程池对象的弊端如下:
FixedThreadPool 和 SingleThreadPool : 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
CachedThreadPool 和 ScheduledThreadPool : 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
无论那种线程池创建方式,都需要调用ThreadPoolExecutor
源码如下
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
参数解释:
- corePoolSize:核心线程数—线程池中实际运行的线程数
- maximumPoolSize:最大线程数—线程池创建线程的最大数量 。值得注意的是,如果使用了无界的任务队列这个参数就没什么效果。
- keepAliveTime:线程池维护线程所允许的空闲时间
- unit: 线程池维护线程所允许的空闲时间的单位 。可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒)。
- workQueue: 线程池所使用的缓冲队列
- handler: 线程池对拒绝任务的处理策略。当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。在JDK 1.5中Java线程池框架提供了以下4种策略。
- AbortPolicy:直接抛出异常。
- CallerRunsPolicy:只用调用者所在线程来运行任务。
- DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
- DiscardPolicy:不处理,丢弃掉