无限制创建线程的不足:
- 线程生命周期的开销。线程的创建和销毁是有代价的,根据平台的不同,实际开销也不同。
- 资源消耗。活跃的线程会消耗系统资源,尤其是内存。而且大量线程在竞争CPU资源时还将残生其他性能开销。
- 稳定性:不同的平台最多可创建线程的数量限制不同,,并且受多个因素制约,包括JVM启动参数、Thread构造函数中请求栈的大小等。
为什么使用线程池?
- 将所有任务放在单个线程中串行执行:糟糕的响应时间和吞吐量
- 为每个任务分配一个线程:资源管理的复杂性
Executor框架:
Public interface Executor{
void execute(Runnable command);
}
Executor 基于生产者-消费者模式,将任务的提交过程和执行过程解耦开来,提交任务的操作相当于生产者(生产待完成的工作单元);执行任务的线程相当于消费者(执行完这些工作单元)。
线程池:
线程池指管理一组同构工作线程的资源池。线程池与工作队列(Work Queue)密切相关。其中工作队列中保存了所有等待执行的任务。工作者线程的任务很简单:从工作队列中获取一个任务,执行任务,然后返回线程池并等待下一个任务。
Java类库提供了一个灵活的线程池以及一些有用的配置。可以通过调用Executors中的静态工厂方法来创建一个线程池:
- newFixedThreadPool :创建一个固定长度的线程池,每当提交一个任务时就创建一个线程,直到达到线程池的做大数量。这时线程池规模将不再发生改变(如果某个线程由于发生了未预期的Exception而结束,那么线程池会补充一个新的线程。
- newCachedThreadPool:将创建一个可缓存的线程池,如果线程池的当前规模超过了处理需求时,那么将回收空闲的线程,而当需求增加时,可以添加新的线程。
- newSingleThreadExecutor:单线程的Executor。它创建单个工作者线程来执行任务,如果这个线程异常结束,会创建另一个线程来替代。能确保依照任务在队列中的顺序来串行执行。
- newScheduledThreadPool:创建一个固定长度的线程池,而且以延迟或定时的方式来执行任务。类似与Timer
通过使用Executor,可以实现各种调优、管理、监视、记录日志、错误报告和其他功能。
Executor的生命周期:
如何关闭Executor?如果不关闭Executor,那么JVM将永远不会结束(JVM在所有非守护线程结束后太会退出)。
- 平缓关闭模式:完成所有已启动的任务,并且不再接收新任务
- 暴力关闭模式:直接关掉电源
为了解决执行服务的生命周期问题,Executor扩展了ExecutorService接口,添加了一些用于生命周期管理的方法。
ExecutorService 的生命周期有三种状态:运行、关闭和已终止。ExecutorService 在初始创建时处于运行状态。
Public interface ExecutorService extends Excutor{
void shutdown(); //执行平缓关闭过程----不再接收新任务,同时等待已提交任务的完成,包括队列中还未开始执行的任务
List<Runnable> shutdownNow(); //执行粗暴关闭过程----它尝试取消所有运行中的任务,并不在启动队列中尚未开始的任务
boolean isShutdown(); //判断是否关闭
boolean isTerminated(); //判断是否终止
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedExecption; //等待终止
//其他用于任务提交的遍历方法
//…
}
Executor执行任务有4个生命周期阶段:创建、提交、开始和完成。由于一些任务可能需要执行很长时间,因此同行希望能够取消此类任务。Executor框架中:
- 已提交但尚未开始的任务可以取消
- 已经开始的任务,只有当它们能相应中断时,才能取消
- 取消已经完成的任务没有任何影响,即一个任务完成后将永远停留在完成状态,无法撤回。因为任务的生命周期只能前进,不能后退。
一般在调用awaitTermnation之后立即调用shutdown,从而产生同步关闭ExectuorService的效果。
Callable和Future:
Executor使用Runnable作为其基本的任务表达形式。Runnable是一种有很大局限的抽象,虽然run能写入日志文件或者将结果放入某个共享的数据结构,但他不能返回一个值或者抛出一个受检查的异常。
Callable是一种更好的抽象:它认为主入口点(即call)将返回一个值,并可以抛出异常。
Future表示一个任务的生命周期,并提供了相应的方法来判断是否已经完成或者取消,以及获取任务的结果和取消任务等。
Public interface Callsble<V>{
V Call() throws Exception;
}
Public interface Future<V>{
boolean cancel (boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException, CancellationException;
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, CancellationException, TimeoutExecption;
}
ExecutorService中所有submit方法都将返回一个Future。将一个Runnable或Callable提交给Executor, 返回一个Future用来获得任务的执行结果或者取消任务。
CompletionService: Executor 与 BlockingQueue
完成任务(CompletionService):如果向Executor提交了一组计算任务,并希望在计算完成后获得结果。可以使用ExecutorService,获取返回的Future反复轮询get方法。但使用CompletionService更加方便。
CompletionService将Executor和BlockingQueue融合在一起。将执行的结果放入BlockingQueue中。然后用take和poll等方法获取完成后的结果。
ExecutorCompletionService实现了C欧美拍了体哦那Service,并将计算部分委托给一个Executor。
参考资料:
https://blog.csdn.net/chuan_yu_chuan/article/details/53390737
任务取消:
什么时候会取消一个任务:
- 用户请求取消
- 有时间限制的操作
- 应用程序事件
- 错误
- 关闭
@ThreadSafe
Public class Test implements Runnable{
private volatile boolean cancelled;
public void run(){
while(!cancelled){
//具体逻辑
}
}
public void cancel(){ cancelled = true; }
}
取消策略:
包括三方面:
- How: 其他代码如何请求取消任务 ----通过调用cancel()方法
- When: 任务何时检查是否取消了请求 ----每次执行具体逻辑前判断
- What: 相应取消时应执行哪些动作 ----如果取消则退出
任务中断:
上面的任务取消有一个严重的问题:如果任务调用了一个阻塞方法,那么任务有可能永远不会检查取消标志,因此线程永远不会结束。比如生产者消费者模式中生产者的具体任务逻辑是:
blockingQueue.put(n++);
如果消费者执行效率远小于生产者效率,那么队列满时生产者线程将会阻塞。然后取消任务执行消费者线程被取消掉,那么生产者线程将会永远阻塞,无法检查取消标志。
所以应该使用任务中断方式。中断是实现取消的最合理的方式。
每个线程都有一个boolean类型的中断状态,当中断线程时,这个线程的中断状态将被置为true。
Public class Thread{
public void interrupt(){ … }
public boolean isInterrupted(){ … }
public static boolean interrupted(){ … }
…
}
对中断操作的正确理解是:它并不会真正地中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一个合适的时刻中断自己。
Public class Test implements Runnable{
public void run(){
while(!Thread.currentThread().isInterrupted()){
//具体逻辑
}
}
public void cancel(){ interrupt(); }
}