线程是否越多越好
线程在java中是一个对象,更是操作系统的资源,线程的创建、销毁都需要时间。如果创建+销毁时间>线程任务的执行时间,创建该线程就会很不合算。
java对象占用堆内存,操作系统线程占用系统内存,根据jvm规范,一个线程默认最大栈大小为1M,这个栈空间是需要从系统内存中分配的,线程过多,会消耗大量内存。
线程数量过多,争抢cpu时间执行分片,操作系统频繁切换线程上下文,会影响性能。
线程并不是越多越好,因此我们可以通过线程池来节约线程开销,避免线程资源浪费。
线程池的基本概念
核心线程数(corePoolSize):当有新任务提交的时候,线程池首先会检查核心线程的数量,如果核心线程都在工作,并且线程数量也已经到达corePoolSize,就不会再新建线程,而是将任务放到任务队列中,等待执行。
当任务执行完后,线程池还是会保持corePoolSize数量的线程,这些线程不会因为空闲而销毁,除非调用allowCoreThreadTimeOut。
等待队列(workQueue):等待队列用于存储当核心线程都在工作的时候,继续新增的任务,核心线程执行完任务之后,会往等待队列里面拉取新的任务进行执行。这个队列一般是一个线程安全的阻塞队列,它的容量可以由开发者自己指定。
最大线程数(maximumPoolSize):当等待队列满了,而当前的线程数还没有超过最大线程数maximumPoolSize,线程池就会新建线程来执行任务。最多可以新建出来maximumPoolSize减去corePoolSize个线程。
线程活动保持时间(keepAliveTime):当线程池的线程总数量大于corePoolSize时,线程池中的空闲的线程保持存活的最大时间。在这个时间内,如果空闲线程没有被使用,多于corePoolSize的线程就会销毁。
拒绝策略(RejectedExecutionHandler):当等待队列已满,同时线程池线程数量也已经达到maximumPoolSize,线程池将会根据拒绝策略来执行后续新加入的任务。开发者可以自定义拒绝策略,默认策略是直接抛弃任务。
关于ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor用于执行定时任务,继承于ThreadPoolExecutor,实现了ScheduledExecutorService。
public class ScheduledThreadPoolExecutor
extends ThreadPoolExecutor
implements ScheduledExecutorService {
//...
}
ScheduledThreadPoolExecutor构造方法的实际是直接调用父类ThreadPoolExecutor的构造函数,通过不同的构造函数可以指定corePoolSize、ThreadFactory、RejectedExecutionHandler。
/*通过父类ThreadPoolExecutor构造出一个指定核心线程数量、最大线程数为Integer.MAX_VALUE,
初始等待队列为16(DelayedWorkQueue的初始容量是16)的线程池*/
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), handler);
}
ScheduledExecutorService定义了四个任务执行方法:
schedule是在指定延时时间后单次调度任务。
scheduleAtFixedRate和scheduleWithFixedDelay,两者都是指定延迟后,周期性执行任务。
区别是当scheduleAtFixedRate遇到当前任务执行时间超过了指定的延迟时间,下一个任务会在当前任务执行完后直接进入执行;而scheduleWithFixedDelay在当前任务执行完后仍然会等待一个延迟时间,然后再执行下一个任务。
public interface ScheduledExecutorService extends ExecutorService {
//指定延时时间后调度执行任务(单次调度)
public ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit);
//指定延时时间后调度执行任务(单次调度)
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay, TimeUnit unit);
//指定延时时间后开始任务,之后每隔period时长,再次执行(周期调度)
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
//指定延时时间后开始任务,前一个任务执行完后,等待delay时长,再次执行(周期调度)
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
}
关于scheduleAtFixedRate和scheduleWithFixedDelay是怎么在底层实现不同延时策略的一个简单分析:
在源码里,这两个方法的唯一区别是在new ScheduledFutureTask的时候,对于第四个参数period的传值区别,前者是unit.toNanos(period),后者是unit.toNanos(-delay)。在后续的setNextRunTime方法中,就会根据这个值来设置任务执行时间time。
源码如下:
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit) {
//...
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(period));
//...
}
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,