-
(1) "1_在线程中执行任务.md"中说到, 串行执行的问题是糟糕的吞吐量和响应性能; 为每个任务分配一个线程的问题是资源消耗过高, 因此好的方式是使用线程池
(2) Java中, 任务执行的主要抽象不是Thread, 而是Executor(java.util.concurrent包中), 它是一个接口, 只提供了一个方法
public interface Executor { /** * Executes the given command at some time in the future. The command * may execute in a new thread, in a pooled thread, or in the calling * thread, at the discretion of the {@code Executor} implementation. * * @param command the runnable task * @throws RejectedExecutionException if this task cannot be * accepted for execution * @throws NullPointerException if command is null */ void execute(Runnable command); }
(3) Executor在java.util.concurrent中的继承接口与实现类见"DelegatedScheduledExecutorService.uml"
(4) Executor基于__生产者-消费者模式__, 提交任务的操作相当于生产者, 执行任务的线程相当于消费者(设计模式中的"命令模式")
(5) 使用Executor实现简单的服务器程序
public class TaskExecutionWebServer { private static final int NTHREADS = 100; private static final Executor exec = Executors.newFixedThreadPool(NTHREADS); public static void main(String[] args) throws IOException { ServerSocket socket = new ServerSocket(80); while (true) { final Socket connection = socket.accept(); Runnable task = new Runnable() { public void run() { handleRequest(connection); } }; exec.execute(task); } } private static void handleRequest(Socket connection) { // request-handling logic here } }
-
由于Executor是一个接口, 因此事实上我们可以用Executor自己实现串行执行方式 和 为每个任务开新线程的方法
1° 串行执行方式的Executor实现
public class WithinThreadExecutor implements Executor { public void execute(Runnable r) { r.run(); } }
2° 为每个任务开新线程的Executor实现
public class ThreadPerTaskExecutor implements Executor { public void execute(Runnable r) { new Thread(r).start(); } }
所以, 事实上各种Executor的实现就是定义了几种常用的模式供调用
-
执行策略
(1) 执行策略定义了如下几个问题
1° 在什么线程执行任务
2° 任务按照什么顺序执行(FIFO, LIFO, 优先级?)
3° 有多少个任务可以并发执行
4° 在队列中有多少个任务在等待执行
5° 如果系统由于过载而需要拒绝一个任务,那么应该选择哪一个
6° 在执行一个任务之前或之后,应该进行哪些操作
(2) Executor通过将任务的提交与任务的执行策略分离开,有助于在部署阶段选择与可用硬件资源最匹配的执行策略
(3) 当希望获得一种更灵活的执行策略时, 应该使用Executor代替Thread
-
线程池
(1) "在线程池中执行任务"相比"为每个任务分配一个线程"的优势
1° 在处理多个请求时, 分摊在线程创建和销毁过程中产生的巨大开销
2° 当请求到达时, 工作线程通常已经存在, 因此不会由于等待创建线程而延迟任务的执行, 从而__提高了响应性__
3° 通过适当调整线程池的大小, 可以创建足够多的线程以使处理器保持忙碌状态;同时防止过多线程互相竞争资源
(2) Executors提供了静态工厂方法用于创建多种不同类型的线程池
1° newFixedThreadPool: 每当提交一个任务时创建一个线程, 直到达到预设的线程池的最大数量
2° newCachedThreadPool: 如果线程池的当前规模超过了处理需求,则回收空闲的线程; 如果需求增加, 则可以添加新的线程
3° newSingleThreadExecutor: 创建单线程的Executor,保证同一时刻只能有一个任务被执行
4° newScheduledThreadPool: 创建一个固定长度的线程池, 并且以延迟或定时的方式执行任务
…
(3) 所有的这些线程池对象, 都继承和实现了Executor接口, 见"DelegatedScheduledExecutorService.uml"
-
ExecutorService
(1) 由于Executor采用__异步__方式执行任务, 在任意时刻提交的任务可能正在运行, 可能运行完成, 可能还在等待; 此时就会有一种需求: 关闭未执行的任务
于是, ExecutorService扩展了Executor接口, 用来管理执行服务的生命周期
(2) ExecutorService接口提供的一些方法
void shutdown(); List<Runnable> shutdownNow(); boolean isShutdown(); boolean isTerminated(); boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
(3) ExecutorService的生命周期有3种状态: 运行、关闭、终止
shutdown()用来平稳的关闭任务, 让已经提交的任务执行完;
shutdownNow()用来粗暴的关闭任务, 尝试取消所有正在执行的任务, 提交但未执行的任务取消之
等所有任务都完成后, ExecutorService将进入终止状态
-
延迟任务与周期任务
(1) Timer类曾经用于管理__延迟任务__(xxx时间后执行某任务) 和 周期任务(每隔xxx时间执行一次某任务), 但是存在一系列的缺陷
(2) Timer类的缺陷
1° Timer支持基于绝对时间的调度机制, 因此任务的执行对系统时钟变化很敏感
2° Timer在执行所有定时任务时只会创建一个线程, 因此如果一个任务执行过长, 会影响后面任务的执行
3° Timer线程不会捕获异常, 因此一个任务抛出异常, 整个Timer线程都会被取消, 后面的任务彻底执行不了了
示例
import static java.util.concurrent.TimeUnit.SECONDS; public class OutOfTime { public static void main(String[] args) throws Exception { Timer timer = new Timer(); timer.schedule(new ThrowTask(), 1); SECONDS.sleep(1); timer.schedule(new ThrowTask(), 1); SECONDS.sleep(5); } static class ThrowTask extends TimerTask { public void run() { throw new RuntimeException(); } } }
这个示例的执行结果是1秒后就结束, 因为第二个timer.schedule(new ThrowTask(), 1);因为Timer已经取消所有根本执行不了
(3) 所以, 不应该用Timer了, 应该用ScheduledThreadPoolExecutor和DelayQueue实现延迟任务和周期任务
chapter06_任务执行_2_Executor框架
最新推荐文章于 2022-10-03 18:41:27 发布