在Java中,使用线程来异步执行任务。
Java线程的创建与销毁需要一定的开销,线程的创建与销毁将消耗大量的计算资源。
为每一个任务创建一个新线程来执行,这种策略可能会使处于高负荷状态的应用最终崩溃。
JDK1.5之后,把工作单元与执行机制分离开来,工作单元包括Runnable和Callable,而执行机制由Executor框架提供。
任务。包括被执行任务需要实现的接口:Runnable接口或Callable接口。
任务的执行。包括任务执行机制的核心接口Executor,以及继承自Executor的 ExecutorService接口。
Executor框架有两个关键类实现了ExecutorService接口 (ThreadPoolExecutor和ScheduledThreadPoolExecutor)。
异步计算的结果。包括接口Future和实现Future接口的FutureTask类。
主线程首先要创建实现Runnable或者Callable接口的任务对象。
工具类Executors可以把一 个Runnable对象封装为一个Callable对象(Executors.callable(Runnable task)或 Executors.callable(Runnable task,Object resule))。
然后可以把Runnable对象直接交给ExecutorService执行(ExecutorService.execute(Runnable command));
也可以把Runnable对象或Callable对象提交给ExecutorService执行(ExecutorService.submit(Runnable task)或ExecutorService.submit(Callable task))。
如果执行ExecutorService.submit(…),ExecutorService将返回一个实现Future接口的对象 (返回FutureTask对象)。
由于FutureTask实现了Runnable,程序员也可 以创建FutureTask,然后直接交给ExecutorService执行。
最后,主线程可以执行FutureTask.get()方法来等待任务执行完成。
主线程也可以执行 FutureTask.cancel(boolean mayInterruptIfRunning)来取消此任务的执行。
ThreadPoolExecutor通常使用工厂类Executors来创建。Executors可以创建3种类型的 ThreadPoolExecutor:SingleThreadExecutor、FixedThreadPool和CachedThreadPool。
- FixedThreadPool适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器。
- SingleThreadExecutor适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景。
- CachedThreadPool是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器。
ScheduledThreadPoolExecutor通常使用工厂类Executors来创建。Executors可以创建2种类型的ScheduledThreadPoolExecutor,
- ScheduledThreadPoolExecutor:适用于需要多个后台线程执行周期任务,同时为了满足资源 管理的需求而需要限制后台线程的数量的应用场景。
- SingleThreadScheduledExecutor:适用于需要单个后台线程执行周期任务,同时需要保证顺 序地执行各个任务的应用场景。
Future接口和实现Future接口的FutureTask类用来表示异步计算的结果。当我们把Runnable 接口或Callable接口的实现类提交(submit)给ThreadPoolExecutor或 ScheduledThreadPoolExecutor时,ThreadPoolExecutor或ScheduledThreadPoolExecutor会向我们 返回一个FutureTask对象
Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。它们之间的区别是Runnable不会返回结果,而Callable可以返回结果。
//Executors提供的,把一个Runnable包装成一个Callable的方法。
public static Callable<Object> callable(Runnable task) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<Object>(task, null);
}
// 内部类
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)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤
需要获取全局锁
)。2)如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。
3)如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(注意,执行这一步骤
需要获取全局锁
)。4)如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用 RejectedExecutionHandler.rejectedExecution()方法。
Executors工具类创建FixedThreadPool线程池,其底层都是通过ThreadPoolExecutor类创建的线程池。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
FixedThreadPool的corePoolSize和maximumPoolSize都被设置为创建FixedThreadPool时指 定的参数nThreads。
keepAliveTime为空闲线程等待新任务的最长时间,超过这个时间后多余的线程将被终止。
当keepAliveTime设置为0L,意味着空闲线程会被立即终止。
1)如果当前运行的线程数少于corePoolSize,则创建新线程来执行任务。
2)当前运行的线程数等于corePoolSize,将任务加入 LinkedBlockingQueue。
3)线程执行完1中的任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
//无界对列
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
FixedThreadPool使用无界队列LinkedBlockingQueue作为线程池的工作队列(队列的容量为 Integer.MAX_VALUE)。
FixedThreadPool使用无界队列作为工作队列会对线程池带来如下影响:
- 当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池中的线程数不会超过corePoolSize。
- 因队列永远不会装满,只有队列装满后,才会使用到maximumPoolSize参数,故使用无界队列时maximumPoolSize将是一个无效参数。
- keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。如果队列中一直存在任务,线程就不会存在空闲时间。况且FixedThreadPool构造函数中keepAliveTime的参数为0L,故使用无界队列时keepAliveTime基本算是一个无效参数。
- 使用无界队列,运行中的FixedThreadPool(未执行方法shutdown()或 shutdownNow())不会拒绝任务(不会调用RejectedExecutionHandler.rejectedExecution方法)。
Executors工具类创建SingleThreadExecutor线程池,其底层都是通过ThreadPoolExecutor类创建的线程池。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
throws NullPointerException if threadFactory is null
*/
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
SingleThreadExecutor线程池和FixedThreadPool线程池原理想通,区别在于:
单线程线程池按照从LinkedBlockingQueue队列中获取任务的先后顺序执行任务。如果任务存在先后顺序,建议使用单线程线程池。
CachedThreadPool是一个会根据需要创建新线程的线程池。可根据实际情况调整线程池中的线程数。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
若有任务,没有空闲线程,则创建线程执行任务;
CachedThreadPool中的空闲线程等待新任务的最长时间为60秒,空闲线程超过60秒后将会被终止。
CachedThreadPool的maximumPool是无界的。这意味着,如果主线程提交任务的速度高于 maximumPool中线程处理任务的速度时,CachedThreadPool会不断创建新线程。极端情况下, CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源。
SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。
1)执行SynchronousQueue.offer(Runnable task)时,如果线程池中有空闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),主线程把任务交给空闲线程执行。
2)当线程池中没有空闲线程,将创建新的线程执行任务。
3)当线程无法通过SynchronousQueue.poll方法取到任务执行,空闲时间超过60秒,空闲线程将被终止