类型
Java通过Executors提供四种线程池,分别为:
-
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
-
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
-
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
-
newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
创建
虽然可以通过Executors.newFixedThreadPool(size)的方式来创建四种线程池,但是不推荐这种做法。因为线程队列 LinkedBlockingQueue没有设置固定容量大小,而且newCachedThreadPool是没有缓存队列的,直接开启新的线程,最终都会导致其无限增大,内存压力山大。
推荐创建方式,自定义ThreadPoolExecutor创建
new ThreadPoolExecutor(8, 16, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue(32), new ThreadPoolExecutor.CallerRunsPolicy());
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
构造函数的参数:
-
corePoolSize 核心线程数
-
maximumPoolSize 线程池中的最大线程数量
-
keepAliveTime 当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁;
-
unit:keepAliveTime的单位
-
workQueue 任务队列,它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种;
-
threadFactory:线程工厂,用于创建线程,一般用默认即可;
-
handler:拒绝策略
AbortPolicy:直接抛出异常,这是默认策略CallerRunsPolicy:用调用者所在的线程来执行任务DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务DiscardPolicy:直接丢弃任务
解析
- 当线程池中线程数小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
- 当线程池中线程数达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行 。
- 当workQueue已满,且maximumPoolSize > corePoolSize时,新提交任务会创建新线程执行任务。
- 当workQueue已满,且提交任务数超过maximumPoolSize,任务由RejectedExecutionHandler处理。
- 当线程池中线程数超过corePoolSize,且超过这部分的空闲时间达到keepAliveTime时,回收这些线程。
- 当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize范围内的线程空闲时间达到keepAliveTime也将回收。
状态
线程池的运行状态:
-
Running:能接受新提交的任务,并且也能处理阻塞队列中的任务;
-
Shutdown:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。(finalize()方法在执行过程中也会调用shutdown()方法进入该状态);
-
Stop:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于
-
RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态;
-
Tidying:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。
-
Terminated:在terminated()方法执行完后进入该状态,默认terminated()方法中什么也没有做。进入terminated的条件如下:线程池不是RUNNING状态;线程池状态不是TIDYING状态或T- ERMINATED状态;如果线程池状态是SHUTDOWN并且workerQueue为空;workerCount为0;设置TIDYING状态成功。
任务提交
使用线程池,可以使用两个方法向线程池提交任务:
-
submit() 返回一个Future对象,通过调用这个对象的get()阻方法,获得返回结果。get()方法会一直阻塞,直到返回结果返回。为了避免任务执行异常被吞掉的问题,我们需要调用Future的get()方法处捕获异常。
-
execute()方法。其中submit有返回值
-
shutdown() 不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
-
shutdownNow()立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
-
awaitTermination(long timeout, TimeUnit unit)作用是执行完字线程再执行主线程,首先该方法会被阻塞,这时会执行子线程中的任务,子线程执行完毕后该方法仍然会被阻塞,因为shutdown()方法还未被调用,而代码中将shutdown的请求放在了awaitTermination之后,这样就导致了只有awaitTermination方法执行完毕后才会执行shutdown请求,这样就造成了死锁。 解决办法自然就很简单了,调换下位置就可以了。
-
isTerminated() 判断所有子线程是否全部执行完毕并关闭,如果全部执行完毕则返回true。注意除非首先调用shutdown或shutdownNow,否则isTerminated永不为true。若关闭后所有任务都已完成,则返回true。
public class Test {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 10,
1, TimeUnit.MINUTES, new LinkedBlockingQueue(30));
FutureTask<String> task = new FutureTask<String>(() -> {
return "sumbit示例";
});
pool.submit(task);
pool.shutdown(); //关闭线程池
try {
String str = task.get();//阻塞
System.out.println(str);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 10,
1, TimeUnit.MINUTES, new LinkedBlockingQueue(30));
pool.execute(() -> {
System.out.println("execute示例");
});
pool.shutdown();
}
}
总结
-
线程池不要申明为本地变量,应该将线程池申明为全局的。
-
自定义ThreadPoolExecutor创建创建线程池,不要使用Executors创建、
-
合理设置线程数。
CPU密集型任务 尽量使用较小的线程池,一般为CPU核心数+1
IO密集型任务 可以使用稍大的线程池,一般为2*CPU核心数