1. 在线程中执行任务
1.1 串行的执行任务
这是最经典的一个最简单的 Socket server 的例子, 服务器的资源利用率非常低, 因为单线程在等待 I/O 操作完成时, CPU 处于空闲状态从而阻塞了当前请求的延迟, 还彻底阻止了其他等待中的请求被处理
- public class SingleThreadwebServer {
- public static void main(String[] args) throws IOException {
- ServerSocket socket = new ServerSocket(80);
- while (true) {
- Socket connection = socket.accept();
- handleRequest(connection);
- }
- }
- private static void handleRequest(Socket connection) {
- // request-handling logic here
- }
- }
1.2 显式地为任务创建线程
任务处理从主线程中分离出来, 主循环可以快速等待下一个连接, 提高响应性同时任务可以并行处理了, 吞吐量也提高了
- public class ThreadPerTaskWebServer {
- 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);
- }
- };
- new Thread(task).start();
- }
- }
- private static void handleRequest(Socket connection) {
- // request-handling logic here
- }
- }
1.3 无限制创建线程的不足
线程的生命周期开销非常高
资源消耗大量的空闲线程占用内存, 给 GC 带来压力, 同时线程数量过多, 竞争 CPU 资源开销太大
稳定性容易引起 GC 问题, 甚至 OOM
2 Executor 框架
任务就是一组逻辑工作单元 (unit of work), 而线程则是使任务异步执行的机制
Executor 接口, 是代替 Thread 来做异步执行的入口, 接口虽然简单, 却为非常灵活强大的异步任务执行框架提供了基础
提供了一种标准的方法将任务的提交与执行过程解耦, 并用 Runnable(无返回时) 或者 Callable(有返回值) 表示任务
Executor 基于生产者 - 消费者模式
提交任务 / 执行任务分别相当于生产者 / 消费者, 通常是最简单的实现生产者 - 消费者设计的方式了
2.1 基于 Executor 改造后的样例如下
将请求处理任务的提交与任务的实际执行解耦, 并且只需采用另一种不同的 Executor 实现, 就可以改变服务器的行为, 其影响远远小于修改任务提交方式带来的影响
2.2 执行策略
这一节主要介绍做一个 Executor 框架需要靠那些点?
在什么线程中执行任务?
任务按照什么顺序执行? FIFO/LIFO / 优先级
有多少个任务可以并发执行?
队列中允许多少个任务等待?
如果系统过载了要拒绝一个任务, 那么选择拒绝哪一个? 如何通知客户端任务被拒绝了?
在执行任务过程中能不能有些别的动作 before/after 或者回调?
各种执行策略都是一种资源管理工具, 最佳的策略取决于可用的计算资源以及对服务质量的要求
因此每当看到
new Thread(runnable).start();
并且希望有一种灵活的执行策略的时候, 请考虑使用 Executor 来代替
2.3 线程池
在线程池中执行任务比为每个任务分配一个线程优势明显:
重用线程, 减少开销
延迟低, 线程是等待任务到达
最大化挖掘系统资源以及保证稳定性 CPU 忙碌但是又不会出现线程竞争资源而耗尽内存或者失败的情况
Executors 可以看做一个工厂, 提供如下几种 Executor 的创建:
- newCachedThreadPool
- newFixedThreadPool
- newSingleThreadExecutor
- newScheduledThreadPool
2.4 Executor 的生命周期
为解决执行服务的生命周期问题, Executor 扩展了 ExecutorService 接口, 添加了一些用于生命周期管理的方法
一个优雅停止的例子:
增加生命周期扩展 Web 服务器的功能
调用 stop
客户端请求形式
关闭
2.5 延迟任务与周期任务
使用 Timer 的弊端在于
如果某个任务执行时间过长, 那么将破坏其他 TimerTask 的定时精确性 (执行所有定时任务时只会创建一个线程), 只支持基于绝对时间的调度机制, 所以对系统时钟变化敏感
TimerTask 抛出未检查异常后就会终止定时线程 (不会捕获异常)
更加合理的做法是使用 ScheduledThreadPoolExecutor, 只支持基于相对时间的调度
它是 DelayQueue 的应用场景
3 找出可利用的并行性
3.1 携带结果的任务 Callable 和 Future
Executor 框架支持 Runnable, 同时也支持 Callable(它将返回一个值或者抛出一个异常)
在 Executor 框架中, 已提交但是尚未开始的任务可以取消, 但是对于那些已经开始执行的任务, 只有他们能响应中断时, 才能取消
Future 非常实用, 他的 API 如下
内部 get 的阻塞是靠 LockSupport.park 来做的, 在任务完成后 Executor 回调 finishCompletion 方法会依次唤醒被阻塞的线程
ExecutorService 的 submit 方法接受 Runnable 和 Callable, 返回一个 FutureThreadPoolExecutor 框架留了一个口子, 子类可以重写 newTaskFor 来决定创建什么 Future 的实现, 默认是 FutureTask 类
3.2 示例: 使用 Future 实现页面的渲染器
3.3 CompletionService: Executor 与 BlockingQueue
计算完成后 FutureTask 会调用 done 方法, 而 CompletionService 集成了 FutureTask, 对于计算完毕的结果直接放在自己维护的 BlockingQueue 里面, 这样上层调用者就可以一个个 take 或者 poll 出来
3.3 示例: 使用 CompletionService 提高渲染性能
- void renderPage(CharSequence source) {
- final List<ImageInfo> info = scanForImageInfo(source);
- CompletionService<ImageData> completionService =
- new ExecutorCompletionService<ImageData>(executor);
- for (final ImageInfo imageInfo : info)
- completionService.submit(new Callable<ImageData>() {
- public ImageData call() {
- return imageInfo.downloadImage();
- }
- });
- renderText(source);
- try {
- for (int t = 0, n = info.size(); t < n; t++) {
- Future<ImageData> f = completionService.take();
- ImageData imageData = f.get();
- renderImage(imageData);
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- } catch (ExecutionException e) {
- throw launderThrowable(e.getCause());
- }
- }
6.3.7 为任务设置时限
Future 的 get 支持 timeout
6.3.8 批量提交任务
使用 invokeAll 方法提交 List<Callable>, 返回一个 List<Future>
来源: http://www.jianshu.com/p/f9cd21aa392a