目录
介绍
在我们的日常开发中,难免会使用到线程,部分还会用到多线程并发问题。我们知道,线程的创建和释放,需要占用不小的内存和资源。如果每次需要使用线程时,都new 一个Thread的话,难免会造成资源的浪费,而且可以无限制创建,之间相互竞争,会导致过多占用系统资源导致系统瘫痪。不利于扩展,比如如定时执行、定期执行、线程中断,所以很有必要了解下ExecutorService的使用。它继承Executor 接口。Executors是工具类。
ExecutorService是Java提供的线程池,也就是说,每次我们需要使用线程的时候,可以通过ExecutorService获得线程。它可以有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞,同时提供定时执行、定期执行、单线程、并发数控制等功能,也不用使用TimerTask了.
ExecutorService的创建方式
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
所有线程池最终都是通过这个方法来创建的。
corePoolSize : 核心线程数,一旦创建将不会再释放。如果创建的线程数还没有达到指定的核心线程数量,将会继续创建新的核心线程,直到达到最大核心线程数后,核心线程数将不在增加;如果没有空闲的核心线程,同时又未达到最大线程数,则将继续创建非核心线程;如果核心线程数等于最大线程数,则当核心线程都处于激活状态时,任务将被挂起,等待空闲线程来执行。
maximumPoolSize : 最大线程数,允许创建的最大线程数量。如果最大线程数等于核心线程数,则无法创建非核心线程;如果非核心线程处于空闲时,超过设置的空闲时间,则将被回收,释放占用的资源。
keepAliveTime : 也就是当线程空闲时,所允许保存的最大时间,超过这个时间,线程将被释放销毁,但只针对于非核心线程。
unit : 时间单位,TimeUnit.SECONDS等。
workQueue : 任务队列,存储暂时无法执行的任务,等待空闲线程来执行任务。
threadFactory : 线程工厂,用于创建线程。
handler : 当线程边界和队列容量已经达到最大时或关闭时,用于处理阻塞时的程序
线程池的类型
可缓存线程池
ExecutorService cachePool = Executors.newCachedThreadPool();看看它的具体创建方式:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
通过它的创建方式可以知道,创建的都是非核心线程,而且最大线程数为Interge的最大值,空闲线程存活时间是1分钟。如果有大量耗时的任务,则不适该创建方式。它只适用于生命周期短的任务。
优点:灵活回收和创建空闲线程
缺点:最大线程数为Integer.MAX_VALUE,容易造成堆外内存溢出。
单线程池
ExecutorService singlePool = Executors.newSingleThreadExecutor();
顾名思义,也就是创建一个核心线程: 。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
优点:只用一个线程来执行任务,保证任务按FIFO顺序一个个执行,当线程执行中出现异常,去创建一个新的线程替换之。
缺点:并发性能不好。
固定线程数线程池
也就是创建固定数量的可复用的线程数,来执行任务,当线程数达到最大核心线程数,则加入队列等待有空闲线程时再执行。
Executors.newFixedThreadPool(3);
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
优点:FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。
缺点:如果线程遇到错误中止,它是无法使用替代线程的。
固定线程数,支持定时和周期性任务
最大线程数依然是 Integer.MAX_VALUE。
ExecutorService scheduledPool = Executors.newScheduledThreadPool(5);
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
可用于替代Timer定时器等延时和周期性任务。与Timer 对比:Timer 的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务(比如:一个任务出错,以后的任务都无法继续)。
public ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit);
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
scheduleAtFixedRate和sheduleWithFixedDelay有什么不同呢?
scheduleAtFixedRate:创建并执行一个在给定初始延迟后的定期操作,也就是将在 initialDelay 后开始执行,然后在initialDelay+period 后下一个任务执行,接着在 initialDelay + 2 * period 后执行,依此类推 ,也就是只在第一次任务执行时有延时。
sheduleWithFixedDelay:创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟,即总时间是(initialDelay + period)*n
单线程执行器
newSingleThreadScheduledExecutor 创建一个单线程执行器,可以调度命令在给定的延迟之后运行,或定期执行。当线程执行中出现异常,不会去创建一个新的线程替换之。或者被try...catch后 继续运行
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
手动创建线程池
private ExecutorService pool = new ThreadPoolExecutor(3, 10,
10L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(512), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
可以根据自己的需求创建指定的核心线程数和总线程数。
ForkJoinPool
newWorkStealingPool :newWorkStealingPool适合使用在很耗时的操作,但是newWorkStealingPool不是ThreadPoolExecutor的扩展,它是新的线程池类ForkJoinPool的扩展。但是都是在统一的一个Executors类中实现,由于能够合理的使用CPU进行对任务操作(并行操作),所以适合使用在很耗时的任务中,实际上是new了一个ForkJoinPool。
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool
(Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
ExecutorService 中submit和Excutor 中execute的区别:ExecutorService继承自Executor,下面是submit的操作,其实都是new一个FutureTask对象,FutureTask里有一个Callable字段,执行的时候执行对应的call方法。但是当提交Runnable任务的时候会通过Executors生成一个Callable的子类对象。RunnableAdapter,这个类是Executors的内部类,运用了适配器的设计模式融合了Callable和Runnable。
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
private static final class RunnableAdapter<T> implements Callable<T> {
private final Runnable task;
private final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
public String toString() {
return super.toString() + "[Wrapped task = " + task + "]";
}
}
1.如果提交的任务不需要一个结果的话直接用execute()会提升很多性能
2.就是相当于说如果你传的任务是需要结果的,那你就使用你的类去继承Callable接口,然后告诉submit方法就行了,如果你想要执行成功后返回自己定义的特定结果,就把那个特定的结果也告诉submit方法。注意:带T的只能是Runable的,并且Future的get方法返回的是你输入进入的结果,也就是result。
如果你不需要一个结果,那么就老老实实使用execute,如果你需要的是一个空结果,那么submit(yourRunnable)与submit(yourRunnable,null)是等价的!最重要的是,submit提交的任务异常时被task捕获的,所以要通过task的get来获取异常,否则异常会被线程池吞掉,而execute的异常不会被吞掉。
ExecutorService的关闭
shutdown和awaitTermination为接口ExecutorService定义的两个方法,一般情况配合使用来关闭线程池。
taskExecutor.shutdown();
while (!taskExecutor.awaitTermination(1, TimeUnit.SECONDS));
shutdown方法:平滑的关闭ExecutorService,当此方法被调用时,ExecutorService停止接收新的任务并且等待已经提交的任务(包含提交正在执行和提交未执行)执行完成。当所有提交任务执行完毕,线程池即被关闭。
awaitTermination方法:当前线程阻塞,直到返回true或者false,接收人timeout和TimeUnit两个参数,用于设定超时时间及单位。当等待超过设定时间时,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。一般情况下会和shutdown方法组合使用,只调用awaitTermination会等所有已提交的任务(包括正在跑的和队列中等待的)执行完,可以继续提交。
shutdownNow
方法:将线程池状态置为STOP。企图立即停止,事实上不一定:跟shutdown()一样,先停止接收外部提交的任务,
忽略队列里等待的任务,尝试将正在跑的任务interrupt中断,返回未执行的任务列表,它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有处理interruptFlag,interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它也可能必须要等待所有正在执行的任务都执行完成了才能退出。但是大多数时候是能立即退出的。
shutdown()和shutdownNow()的区别:
从字面意思就能理解,shutdownNow()能立即停止线程池,正在跑的和正在等待的任务都停下了。这样做立即生效,但是风险也比较大; shutdown()只是关闭了提交通道,用submit()是无效的;而内部该怎么跑还是怎么跑,跑完再停。
shutdown()和awaitTermination()的区别
shutdown()后,不能再提交新的任务进去;但是awaitTermination()后,可以继续提交。 awaitTermination()是阻塞的,返回结果是线程池是否已停止(true/false);shutdown()不阻塞。
isShutdown() 表示此线程池是否关闭。一般在提交任务前执行来判断线程池状态。
线程池原理:
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {//核心线程是否满了
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {//放入队列
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))//没超过最大线程会创建线程执行
reject(command);//执行失败handler
}
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//这块代码是在创建非核心线程时,即core等于false。
//判断当前线程数是否大于等于maximumPoolSize,如果大于等于则返回false
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);//创建Worker对象,同时也会实例化一个Thread对象
//worker实现了runnable
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();//启动启动这个线程,并调用work里的run方法。
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
//如果task 不为null或者getTask方法从workerQueue里读取任务不为null
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();//执行任务
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//判断当前线程数是否大于corePoolSize。
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
//如果当前线程数大于核心线程数,则会调用workQueue的poll方法获取任务,
//超时时间是keepAliveTime。如果超过keepAliveTime时长,poll返回了null,
//上边提到的while循序就会退出,线程也就执行完了。
//如果当前线程数小于corePoolSize,则会调用workQueue的take方法阻塞在当前
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
注意runWorker里while循环,如果参数task执行完毕后还会通过getTask方法判断队列是否有任务,如果有就执行。没有就break推出循环。
ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor的主要特点是两个内部类ScheduledFutueTask和DelayedWorkQueue,实际上这也是线程池工作流程中最重要的两个关键因素:任务以及阻塞队列。现在我们来看下ScheduledThreadPoolExecutor提交一个任务后,整体的执行过程。以ScheduledThreadPoolExecutor的schedule方法为例,具体源码为:
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay,
TimeUnit unit) {
if (callable == null || unit == null)
throw new NullPointerException();
//将提交的任务转换成ScheduledFutureTask
RunnableScheduledFuture<V> t = decorateTask(callable,
new ScheduledFutureTask<V>(callable,
triggerTime(delay, unit)));
//延时执行任务ScheduledFutureTask
delayedExecute(t);
return t;
}
private void delayedExecute(RunnableScheduledFuture<?> task) {
if (isShutdown())
//如果当前线程池已经关闭,则拒绝任务
reject(task);
else {
//将任务放入阻塞队列中
super.getQueue().add(task);这个Queue就是我们刚才看到的DelayedWorkQueue
if (isShutdown() &&
!canRunInCurrentRunState(task.isPeriodic()) &&
remove(task))
task.cancel(false);
else
//保证至少有一个线程启动,即使corePoolSize=0
ensurePrestart();
}
}
void ensurePrestart() {
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
addWorker(null, true);
else if (wc == 0)
addWorker(null, false);
}
public boolean offer(Runnable x) {
if (x == null)
throw new NullPointerException();
RunnableScheduledFuture e = (RunnableScheduledFuture)x;
final ReentrantLock lock = this.lock;
lock.lock();
try {
int i = size;
if (i >= queue.length)
grow();//扩容
size = i + 1;
if (i == 0) {//如果是第一个元素
queue[0] = e;
setIndex(e, 0);
} else {//如果不是,那么进行上滤操作,保证堆有序
siftUp(i, e);
}
//如果当前新增加的元素在堆顶,那么可能是最新要执行的
if (queue[0] == e) {
leader = null;
available.signal();//发一个信号,通知take的时候的线程,赶紧检测新加入的task
}
} finally {
lock.unlock();
}
return true;
}
这是DelayedWorkQueue的offer方法,DelayedWorkQueue里面是使用数组去维护任务队列的,那么数组是怎么保证任务有序呢?
其实仔细看代码,我们能发现,这里的实现是用一个二叉堆去对数组元素进行排序。确切的说是小顶堆。
首先判断容量,如果容量不够就扩容,接着判断是不是第一个元素,如果是,那么直接放在index为0的位置,不是的话进行上滤操作。接下来判断添加的元素是不是在堆顶,如果是那么需要进行优先调度,那么进行signal。
其实这里又引申出一个问题,那么就是是靠什么排序的?这个时候我们看一下任务的实体ScheduledFutureTask,它复写了compareTo方法
public int compareTo(Delayed other) {
if (other == this) // compare zero if same object
return 0;
if (other instanceof ScheduledFutureTask) {
ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;
long diff = time - x.time;//根据time去比较,time是在创建任务的时候计算出来的,指下一次运行的时间
if (diff < 0)
return -1;
else if (diff > 0)
return 1;
else if (sequenceNumber < x.sequenceNumber)
return -1;
else
return 1;
}
long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
}
里面用time去判断大小,time便是下一次调度的时间点,那么显然越小的离现在越近,越要放在前面。
看了offer方法我们再看看take方法,这个方法是用来获取任务的:
public RunnableScheduledFuture<?> take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();//阻塞
//队列的存储采用数组,优先级排序采用二叉堆实现。
try {
for (;;) {
//第一个是最先应该被执行的
RunnableScheduledFuture<?> first = queue[0];
if (first == null)
available.await();
else {
//获取第一个任务,是距离当前最近的任务,下面获取延迟
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)//要立即执行
return finishPoll(first);
first = null; // don't retain ref while waiting
if (leader != null)
available.await();//拿不到leader的线程全部await
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
//如果此时线程唤醒了,那么其他线程将不能进入同步块//
available.awaitNanos(delay);//等待延迟时间过去
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && queue[0] != null)
available.signal();//唤醒所有的await线程
lock.unlock();
}
}
毫无疑问,take中直接获取queue[0],它是距离目前最近的要被执行的任务,先检测一下还有多长时间,任务会被执行,如果小于0,那么立刻弹出,并且做一个下滤操作,重新找出堆顶元素。如果不小于0,那么证明时间还没到,那么available.awaitNanos(delay);等到delay时间后自动唤醒,或者因为添加了一个更加紧急的任务即offer中的signal被调用了,那么唤醒,重新循环获取最优先执行的任务,如果delay小于0,那么直接弹出任务。
至此任务调度的逻辑分析完了,但是还有周期执行是怎么实现的呢?其实是在ScheduledFutureTask的run中实现的:
public void run() {
boolean periodic = isPeriodic();//判断是不是定时周期调度的
if (!canRunInCurrentRunState(periodic))
cancel(false);
else if (!periodic)
ScheduledFutureTask.super.run();
else if (ScheduledFutureTask.super.runAndReset()) {执行后futureTask状态设置为new
setNextRunTime();//计算下一次执行时期
reExecutePeriodic(outerTask);//加入队列
}
}
判断是不是周期调度的任务,如果是等待执行完毕之后,重新设置下一次执行时间,并且将此任务重新offer到queue中,这样就实现了周期调度。
问题:
其实这里是有一个问题的,就是如果当核心线程池比较少,但是执行的任务又有很多阻塞性的任务,那么就会导致在任务到期该执行的时候,而没有线程去执行任务。这样就会导致任务调度时间不准,同时后面的任务也可能被影响,所以在设置的时候可以将自己的核心线程池调大一点,避免这种问题。