1、存在风险的线程池创建方式–慎用
创建线程池的方式多种多样,但下面三种不用用在正式环境中,它们分别是:
- FixedThreadPool:固定大小的线程池
- SingleThreadExecutor:单个线程的线程池
- CachedThreadPool:可缓存的线程池
这三种创建方式都在Executors工具类中
这些创建线程池的方式都是基于原生创建线程池的方式衍生出来的,我们掌握了原生创建方式,这些创建方式自然也就明白了。
2、Executors线程池工具类
- FixedThreadPool:固定大小的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
核心线程数和最大线程数相同,意味着该线程池中的线程都是核心线程,空闲线程就不会被销毁。任务队列为链式阻塞队列,此队列有资源耗尽的风险,因为LinkedBlockingQueue的容量为Integer的最大值231,意味着任务队列中的任务数量可高达21(2147483657)之多,容易内存爆表。
- SingleThreadExecutor:单个线程的线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
核心线程数和最大线程数相同,都为1,意味着等待唯一的单线程来执行任务,并保证所有任务按照指定顺序(FIFO或优先级)执行。和FixedThreadPool是一样的问题,此队列有资源耗尽的风险。创建单个线程的线程池有两个方法,一个不带线程工厂,一个带线程工厂。
输出结果:
"C:\Program Files\Java\jdk1.8.0\bin\java.exe"
pool-1-thread-1, index=0
pool-1-thread-1, index=1
pool-1-thread-1, index=2
从运行结果可以看出,所有任务都是在单一线程运行的。
- CachedThreadPool:可缓存的线程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
可缓存的线程池意味着线程池里面全都是非核心线程最大线程数是Integer的最大值,空闲线程60秒内没工作就会被销毁风险不言而喻,规避资源耗尽的风险。创建可缓存线程的线程池也有两个方法,一个不带线程工厂,一个带线程工厂。
3、提交任务的2种方式
- execute
该方法位于Executor接口中,作用是向线程池中提交Runnable任务,Runnable的任务是无返回值的任务。因此该方法 只适合提交无返回值的任务,执行完没有结果返回。如果任务是有返回值的,就需要创建Callable任务,它是一个有返回值的任务,Callable任务执行完,会将执行结果封装到Future对象中,然后返回给调用者,调用者再通过Future对象获取结果。
当该方法提交的任务被拒绝,则抛出任务拒绝异常,提交的任务不能为Null,否则会抛出空指针异常。
-
submit
submit方法位于Executor Service接口中,它可以提交Callable任务,也可以提交Runnable任务,方法返回一个Future对象,可以看到该对象无返回值,但是为什么还要返回Future对象呢?这是因为Future除了获取任务的执行结果之外,还可以观察任务是否执行完毕以及取消任务。
两种方法区别如下: