任务是一组逻辑工作单元,而线程则是使任务异步执行的机制。
当把所有任务都放在单个线程中串行执行时,会产生糟糕的响应性;
“为每个任务分配一个线程”资源管理的复杂性会升高,不足如下:
- 线程生命周期的开销非常高。(创建和销毁)
- 资源消耗
活跃资源消耗资源,尤其内存。当可运行线程大于可用处理器时,有些线程将闲置,从而闲置线程占用许多内存,给垃圾回收器带来压力,而且在竞争CPU会有其他性能消耗。 - 稳定性
在可创建线程的数量存在一个限制。受多个因素影响(平台,JVMI启动参数,、Thread构造函数请求栈大小以及底层操作系统对线程的限制,破坏了限制可能抛出OOM异常)
线程池简化了线程的管理工作。在Java类库中,任务执行的抽象不是Thread,而是Executor,它是一个异步框架,将任务提交和执行解耦开来。基于生产者(提交任务)-消费者(执行任务)模型.
线程池,是指管理一组同构工作线程的资源池。线程池与工作队列相关,其中在工作队列中保存了所有的等待任务。工作者线程每次从工作队列获取一个任务,执行,然后返回线程池执行下一个任务。
线程池减少了线程创建销毁过程中的开销,另外一个好处是,当请求到达时,工作线程已经存在,因此不会犹豫等待创建线程而延迟任务的执行,从而提高了性能。适当的调整线程池的大小,可以创建足够多的线程以便使CPU保持足够忙碌,同时可以防止过多的线程相互竞争而使应用程序耗尽内存而失败。
Java类库可以通过调用Executors的静态工厂方法来创建线程:
- newFixedThreadPool.创建一个固定长度的线程池,每提交一个任务创建一个线程,直到达到最大值,便不会增加,当某个线程发生异常时,会补充一个线程。
- newCachedThreadPool.将创建一个缓存线程,如果线程池当前规模超过了处理的请求,将回收线程,反之,增加线程,线程池规模不受限制,即动态变化。
- newSingleThreadPool。单线程的Executor,创建单线程执行任务,当有,异常,创建另一个线程来替代。
- newScheduledThreadPool。 创建了一个固定长度的线程池,而以延迟或者定时的方式来执行。
Executor生命周期
Executor通过创建线程来执行任务。JVM只有在所有非守护线程结束后才会退出。因此需要需要正确的关闭Executor,才能使JVM完全结束。
ExecutorService扩展了Executor接口,增加了生命周期的管理方法。
ExecutorService生命周期有三种状态:运行,关闭,终止。
- shutdown():将执行平缓的关闭过程,完成所有已经启动的任务,并不再接收新任务
- shutdownNow():将执行粗暴的过程,尝试取消所有运行中的任务。并不再启动队列中尚未启动开始执行的任务。
- awaitTermination():等待ExecutorService到达终止状态。
- isTerminated():判断ExecutorService是否终止。
ExecutorService的主要方法
- Future submit(Callable).提交任务
- List<Future> invokeAll(Collection<? extends Callable> tasks, long timeout, TimeUnit unit)执行一个Callable集合,返回超时时间内所有完成的任务
- T invokeAny(Collection<? extends Callable> tasks, long timeout, TimeUnit unit)当有一个执行成功的即返回
延迟任务和周期任务
Timer类负责管理延迟任务和周期任务。
缺陷:
- 执行定时任务只会创建单线程。如果某个任务执行时间过长,将破坏其他定时精确性。
- 当抛出异常,并不会捕获,将终止定时任务,不会回复线程运行,而是整个Timer都被取消了。(线程泄露)
所以应该考虑使用ScheduledThreadPoolExecutor来代替它。