目录
CachedThreadPool
缓存线程池:使用方式
ExecutorService cT= Executors.newCachedThreadPool();
/**
创建一个线程池,根据需要创建新线程,但在可用时将重用先前构造的线程。 这些池通常会提高执行许多短期异步任务的程序的性能。 如果可用,调用execute将重用先前构造的线程。 如果没有可用的现有线程,则会创建一个新线程并将其添加到池中。 60 秒内未使用的线程将被终止并从缓存中删除。 因此,保持空闲足够长时间的池不会消耗任何资源。 请注意,可以使用ThreadPoolExecutor构造函数创建具有相似属性但细节不同(例如,超时参数)的ThreadPoolExecutor 。
返回:
新创建的线程池
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
可以看出,其线程全为非核心线程,其在空闲时会计算生存时间,这里为60s,生存时间过了就会销毁线程,另外,由于工作队列用的是同步移交队列,即作为一个中介工厂生产队列在这阻塞,直到需要执行新任务时在这拿。这里的问题在于线程的最大线程数过大,如果很多任务都要同时需要执行的时候,都会创建一个新线程。这样因为每个线程都有一个独立的虚拟机栈空间 Xss默认为1M,当有10000个任务时至少需要越10g的内存来创建这些线程,很容易OOM。
FixedThreadPool
固定数目线程池:所有任务只能使用固定大小的线程;
ExecutorService fT = Executors.newFixedThreadPool(10);
/**
创建一个线程池,该线程池重用固定数量的线程在共享的无界队列中运行。 在任何时候,最多nThreads线程将是活动的处理任务。 如果在所有线程都处于活动状态时提交了其他任务,它们将在队列中等待,直到有线程可用。 如果任何线程在关闭前的执行过程中因失败而终止,则在需要执行后续任务时,将有一个新线程取而代之。 池中的线程将一直存在,直到它被明确shutdown 。
参数:
nThreads – 池中的线程数
返回:
新创建的线程池
抛出:
IllegalArgumentException – 如果nThreads <= 0
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
/**
创建LinkedBlockingQueue ,容量Integer.MAX_VALUE 。
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
可以看出,其线程全为核心线程,没有非核心线程,即这里面的线程均为常驻线程(默认不关闭线程池就不会销毁里面的线程)。但是值得注意的是其中的工作队列用的是无界(容量)的阻塞队列。这存在一个问题,这意味着永远不会走拒绝策略,当同时存在很多任务打到线程池时,会创建很多待执行任务积压在队列里面,也容易造成OOM。
SingleThreadExecutor
/**
创建一个 Executor,它使用单个工作线程在无界队列中运行。 (但是请注意,如果这个单线程在关闭之前由于执行失败而终止,如果需要执行后续任务,一个新线程将取代它。)保证任务按顺序执行,并且不会超过一个任务处于活动状态在任何给定的时间。 与其他等效的newFixedThreadPool(1) ,返回的执行器保证不可重新配置以使用其他线程。
返回:
新创建的单线程 Executor
*/
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
FixedThreadPool的特殊版本,即只能创建一个新的线程,其余的待执行任务s都会积压在队列里面。
ScheduledThreadPool
周期性线程池:定时以及周期地执行任务。
/**
创建一个线程池,可以安排命令在给定延迟后运行,或定期执行。
参数:
corePoolSize – 要保留在池中的线程数,即使它们处于空闲状态
返回:
新创建的调度线程池
抛出:
IllegalArgumentException – 如果corePoolSize < 0
*/
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
/**
使用给定的核心池大小创建一个新的ScheduledThreadPoolExecutor 。
参数:
corePoolSize – 要保留在池中的线程数,即使它们处于空闲状态,除非设置了allowCoreThreadTimeOut
抛出:
IllegalArgumentException – 如果corePoolSize < 0
*/
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
/**
池线程的默认保持活动时间。 通常,此值未使用,因为所有池线程都将是核心线程,但是如果用户创建一个 corePoolSize 为零的池(违反我们的建议),只要有排队的任务,我们就会保持线程处于活动状态。 如果保持活动时间为零(历史值),我们最终会在 getTask 中进行热旋转,浪费 CPU。 但另一方面,如果我们将该值设置得太高,并且用户创建了一个他们没有完全关闭的一次性池,则池的非守护线程将阻止 JVM 终止。 一个小的但非零的值(相对于 JVM 的生命周期)似乎是最好的。
*/
private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;
这里面的工作队列使用的是延时队列DelayedWorkQueue,总的来说,ScheduledThreadPool能做这样一些事情:
- 延时执行任务,可以指定任务提交给线程池之后延迟多久才执行.
pool.schedule(()->{
System.out.println("延时5s");
},5,TimeUnit.SECONDS);
- 周期执行任务,希望它能周期反复执行某项任务。
AtomicInteger count= new AtomicInteger();
pool.scheduleAtFixedRate(()->{
System.out.println(count.incrementAndGet());
},2,1,TimeUnit.SECONDS); //初始延迟时间为2s,然后周期反复每隔1s执行
总结
可以看出这些在Executors类中提供的便捷线程池对象都是基于ThreadPoolExecutor的,《阿里巴巴开发手册》说过,不建议我们直接这样使用,因为线程池的参数设置都需要看具体环境,一般不设置无界队列和Integer.MAX_VALUE这样的线程数。