1.为什么要使用线程池
- 单线程执行任务必须串行的执行,那么如果某个任务需要很长的计算时间或者发生IO等阻塞,那么后面的任务必须长时间的等待。并且在多处理器环境中,单线程只占用一个处理器,其他处理器得不到利用。
- 假设为每一个任务创建一个线程(即无限制的创建线程),任务执行完之后销毁线程。那么频繁的创建和销毁线程需要很大的开销;并且当线程的数量远大于处理器的数量时,大量的线程需要在内存中等待并占用内存;随着内存中等待线程数量的增多,最终会耗尽内存。
- 而线程池是以上两种情况的折中。线程池中维护着一定数量的线程,每个任务获取到一个空闲的线程,任务执行完毕后这个线程将会被再次利用,当没有空闲线程时,任务必须等待。这样既提高了CPU的利用率,又减少了内存消耗。
- 当所有任务的执行执行时候都很短并且在单处理器环境中时,使用单线程策略是合理的。
2.Executor框架
Executor框架的思路就是将任务的提交与执行分开,具体的执行策略可以选择不同的线程池:如单线程,多线程等。
2.1 Executor接口
public interface Executor {
void execute(Runnable command);
}
Executor接口是整个Executor框架的基础,它提供了一种标准的方法来将任务的提交与执行分开,并用Runnable来表示任务。
Executor executor = anExecutor; //创建一个任务的执行者
executor.execute(new RunnableTask1());//将任务提交给Executor,具体的执行方法由Executor决定
executor.execute(new RunnableTask2());
Executor处理任务的方法是由execute方法决定而不是任务的提交线程,执行的策略可以是由提交任务的线程执行,创建一个线程执行,交个一个单线程执行,交给线程池等等。
//交给提交线程执行:
class DirectExecutor implements Executor {
public void execute(Runnable r) {
r.run();
}
}
//另外创建线程执行:
class ThreadPerTaskExecutor implements Executor {
public void execute(Runnable r) {
new Thread(r).start();
}
}
2.2 ExecutorService接口
- Executor接口的问题:虽然将任务提交与执行分开了,但是任务提交之后,任务采用一定策略被执行直到所有的任务都执行完毕,那么Executor的生命周期也就结束了。但是提交线程无法查看任务的执行情况(成功执行还是发生了错误),并且Executor的生命周期也得不到管理。
- ExecutorService接口扩展了Executor接口,它提供了管理线程池的方法以及查看任务执行情况的方式。ExecutorSecvice的生命周期是运行,关闭,已终止。
interface ExecutorService extends Executor {
void shutDown();
List<Runnable> shutDownNow();
boolean awaitTermination(long timeout, TimeUnit unit);
Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
Future<T> submit(Runnable task, T result);
List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
....
}
- shutDown采用平缓的关闭过程,不在接受提交任务,但是已经提交的任务会继续执行直到结束。
- shutDownNow采用强硬的关闭过程,不接受提交任务并且尝试取消所以已提交但是没有运行结束的任务,并且返回已经提交但是还没有开始执行的任务。
- awaitTermination阻塞直到任务全部完成或者时间超时,通常调用之后会接着调用shutDown,因为任务没有在你期望的时间内结束,所以需要终止他们。
- submit类似于excute方法,只是使用submit提交任务之后会返回一个用于查看任务执行情况的接口Future(立即返回)。
- invokeAll用于批量提交任务,但是与submit不同的是,它需要阻塞直到所有的任务都执行完毕才会返回Future队列。此外还可以设置阻塞时间(阻塞时间到了之后将返回执行完的任务Future,未完成的任务都将取消)。
2.3 Callable和Future
- Callable和Runnable非常相似,不同之处在于Runnable不能抛出异常及返回结果但是Callable可以。所以可以用Callable执行一段任务最后返回一个执行结果。
public interface Callable<V> {
V call() throws Exception;
}
- Future是提交任务之后返回的用于查看任务执行结果的接口。
- cancel(true)用于取消任务,isCancelled()用于判断任务是否取消
- isDone()用来判断任务是否已完成
- get()方法的行为取决于任务的状态,如果任务已经完成,那么他将立即返回执行结果或者抛出异常,如果任务没有完成,他将阻塞直到任务完成。如果任务被取消,将抛出CancellationException。但是在invoke方法中,isDone一定是返回true。
- 如果使用Runnable作为任务提交的方式并且没有设置返回结果,那么get会得到null。
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get();
V get(long timeout, TimeUnit unit);
}
此外,可以使用FutureTask显示的提交Runnable或者Callable任务。因为FutureTask实现了RunnableFuture,而RunnableFuture继承了Runnable和Future接口,因此FutureTask可以使用executor.execute(futureTask)提交。
FutureTask<String> future =
new FutureTask<String>(new Callable<String>() {
public String call() {
return searcher.search(target);
}});
executor.execute(future);
2.4 ScheduledExecutorService接口
- ScheduledExecutorService接口继承并扩展了ExecutorService接口。可以用来提交延迟和周期任务。
- ScheduledExecutorService接口主要扩展了四个方法,schedule用来设置延迟任务,scheduleAtFixedRate和scheduleWithFixedDelay用来设置周期任务。
- ScheduledExecutorService使用的时间都是相对时间而非绝对时间。
- scheduleAtFixedRate的执行时间依次是: initialDelay,initialDelay+period, initialDelay + 2 * period, and so on。
- scheduleWithFixedDelay第一次执行的时间是initialDelay,之后每次执行时间是上一次执行结束后等待period时间,然后执行。
- 未执行完成的任务可以通过cancel取消,对于周期任务,如果某个任务抛出异常,那么后续任务不在执行。否则只有通过cancel以及关闭服务来取消。
public interface ScheduledExecutorService extends ExecutorService {
ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);
....
}
Executors工厂
Executors用来实例化各种类型的Executor(ExecutorService,ScheduledExecutorService)。
class Executors {
static ExecutorService newCachedThreadPool();
static ExecutorService newFixedThreadPool(int nThreads);
static ExecutorService newSingleThreadExecutor();
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);
static ScheduledExecutorService newSingleThreadScheduledExecutor();
.....
}
- newCachedThreadPool:创建一个可缓存的线程池,如果线程空闲时间超过sixty seconds就会被销毁,当没有空闲线程可以执行新的任务时会创建新的线程,因此线程数量会动态变化,并且线程的规模没有限制。(不需要任务等待队列)
- newFixedThreadPool:创建一个线程数量固定的线程池,如果某个线程抛出Exception而意外终止,会创建一个新的线程来维持线程的数量固定。当有新的任务到来并且没有空闲的线程时,任务会进入一个无边界的队列中等待。
- newSingleThreadExecutor:创建一个单线程的Executor,如果这个线程异常结束,会创建一个新的线程替代它,他能保证提交的任务串行执行(FIFO,LIFO,优先级)。
2.4 CompletionService && ExecutorCompletionService
CompletionService接口的作用是将Executor的任务执行与结果返回分离,由CompletionService来管理任务执行完毕后返回的结果。所有已完成的结果会加入到CompletionService维护的阻塞队列当中,用户只需要反复调用take方法来取回执行结果,ExecutorCompletionService实现了接口CompletionService。
public class ExecutorCompletionService implements CompletionService {
ExecutorCompletionService(Executor executor);
ExecutorCompletionService(Executor executor, BlockingQueue<Future<V>> completionQueue);
Future<V> poll();
Future<V> poll(long timeout, TimeUnit unit);
Future<V> take();
Future<V> submit(Callable<V> task);
Future<V> submit(Runnable task, V result);
}
- take()方法从阻塞队列中取出一个任务执行结果,如果没有执行完成的结果则会阻塞。
- poll()方法从阻塞队列中取出一个任务执行结果,如果没有执行完成的结果则会返回null,不会阻塞。
2.5 ThreadPoolExecutor和ScheduledThreadPoolExecutor自定义线程池
- 除了使用Executors工厂实例化线程池,还可以使用ThreadPoolExecutor和ScheduledThreadPoolExecutor这两个类来自定义线程池。
- 实例化线程池时需要指定线程池基本大小,最大大小,存活时间等,而使用工厂则是工厂已经帮我们设置好了这些参数。直接返回一个实例化的类。
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler){...}