第6章 任务执行
在并发应用中,避免为每个任务都分配一个线程
- 线程生命周期的开销很高,在请求到达率很高的情况下将耗费大量计算资源影响性能
- 资源消耗大,可运行的线程数超过CPU数量后,必定会有线程被闲置等待CPU时间片,但是其仍然占用内存保存其状态,给GC带来压力。而且大量线程竞争CPU的时候额外的性能开销也不可忽视
- 稳定性,无限制的创建线程将难以避免服务器在高负载或遭到恶意攻击时崩溃,因此需要对应用程序可创建的线程数量进行限制,并进行全面的测试
Executor框架
/** * An object that executes submitted {@link Runnable} tasks. This * interface provides a way of decoupling task submission from the * mechanics of how each task will be run, including details of thread * use, scheduling, etc. An {@code Executor} is normally used * instead of explicitly creating threads. For example, rather than * invoking {@code new Thread(new(RunnableTask())).start()} for each * of a set of tasks, you might use: * ** * More typically, tasks are executed in some thread other * than the caller's thread. The executor below spawns a new thread * for each task. * ** Executor executor = anExecutor; * executor.execute(new RunnableTask1()); * executor.execute(new RunnableTask2()); * ... ** * However, the {@code Executor} interface does not strictly * require that execution be asynchronous. In the simplest case, an * executor can run the submitted task immediately in the caller's * thread: * *{@code * class DirectExecutor implements Executor { * public void execute(Runnable r) { * r.run(); * } * }}
{@code * class ThreadPerTaskExecutor implements Executor { * public void execute(Runnable r) { * new Thread(r).start(); * } * }}* * Many {@code Executor} implementations impose some sort of * limitation on how and when tasks are scheduled. The executor below * serializes the submission of tasks to a second executor, * illustrating a composite executor. * *
{@code * class SerialExecutor implements Executor { * final Queue
Memory consistency effects: Actions in a thread prior to * submitting a {
@code Runnable} object to an {@code Executor} * happen-before * its execution begins, perhaps in another thread. * * @since 1.5 * @author Doug Lea */ 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); }上面展示了Executor接口的Java源代码,它提供了一种标准的方法将任务的提交过程与执行过程解耦合,并用Runable表示一个可执行的任务。
当你需要改变任务执行的方式,你只需要改变executor的实现,而无需影响本身提交+执行的骨干代码。
线程池
线程在创建和销毁过程中是有巨大开销的,线程池的设计思路就是重用已有线程,提高响应速度,并保证适当的线程数量使得处理器可以保持在忙碌状态,并且控制住线程竞争造成的内存消耗。
Executor提供了工厂方法来创建线程池:
newFixedThreadPool:固定长度的线程池
newCachedThreadPool:根据处理需求回收和增加线程数,没有规模限制
newSingleThreadPool:单一线程,可以保证任务的顺序(按优先级)执行
newScheduledThreadPool:固定长度的线程池,并且以定时或者延迟的方式来执行任务
ExecutorService
package java.util.concurrent; import java.util.List; import java.util.Collection; /** * An {@link Executor} that provides methods to manage termination and * methods that can produce a {@link Future} for tracking progress of * one or more asynchronous tasks. * ** * The following method shuts down an {@code ExecutorService} in two phases, * first by calling {@code shutdown} to reject incoming tasks, and then * calling {@code shutdownNow}, if necessary, to cancel any lingering tasks: * *An {
@code ExecutorService} can be shut down, which will cause * it to reject new tasks. Two different methods are provided for * shutting down an {@code ExecutorService}. The {@link #shutdown} * method will allow previously submitted tasks to execute before * terminating, while the {@link #shutdownNow} method prevents waiting * tasks from starting and attempts to stop currently executing tasks. * Upon termination, an executor has no tasks actively executing, no * tasks awaiting execution, and no new tasks can be submitted. An * unused {@code ExecutorService} should be shut down to allow * reclamation of its resources. * *Method {
@code submit} extends base method {@link * Executor#execute(Runnable)} by creating and returning a {@link Future} * that can be used to cancel execution and/or wait for completion. * Methods {@code invokeAny} and {@code invokeAll} perform the most * commonly useful forms of bulk execution, executing a collection of * tasks and then waiting for at least one, or all, to * complete. (Class {@link ExecutorCompletionService} can be used to * write customized variants of these methods.) * *The {
@link Executors} class provides factory methods for the * executor services provided in this package. * *Usage Examples
* * Here is a sketch of a network service in which threads in a thread * pool service incoming requests. It uses the preconfigured {@link * Executors#newFixedThreadPool} factory method: * *{@code * class NetworkService implements Runnable { * private final ServerSocket serverSocket; * private final ExecutorService pool; * * public NetworkService(int port, int poolSize) * throws IOException { * serverSocket = new ServerSocket(port); * pool = Executors.newFixedThreadPool(poolSize); * } * * public void run() { // run the service * try { * for (;;) { * pool.execute(new Handler(serverSocket.accept())); * } * } catch (IOException ex) { * pool.shutdown(); * } * } * } * * class Handler implements Runnable { * private final Socket socket; * Handler(Socket socket) { this.socket = socket; } * public void run() { * // read and service request on socket * } * }}
{@code * void shutdownAndAwaitTermination(ExecutorService pool) { * pool.shutdown(); // Disable new tasks from being submitted * try { * // Wait a while for existing tasks to terminate * if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { * pool.shutdownNow(); // Cancel currently executing tasks * // Wait a while for tasks to respond to being cancelled * if (!pool.awaitTermination(60, TimeUnit.SECONDS)) * System.err.println("Pool did not terminate"); * } * } catch (InterruptedException ie) { * // (Re-)Cancel if current thread also interrupted * pool.shutdownNow(); * // Preserve interrupt status * Thread.currentThread().interrupt(); * } * }} * *
Memory consistency effects: Actions in a thread prior to the * submission of a {
@code Runnable} or {@code Callable} task to an * {@code ExecutorService} * happen-before * any actions taken by that task, which in turn happen-before the * result is retrieved via {@code Future.get()}. * * @since 1.5 * @author Doug Lea */ public interface ExecutorService extends Executor { /** * Initiates an orderly shutdown in which previously submitted * tasks are executed, but no new tasks will be accepted. * Invocation has no additional effect if already shut down. * *This method does not wait for previously submitted tasks to * complete execution. Use {
@link #awaitTermination awaitTermination} * to do that. * * @throws SecurityException if a security manager exists and * shutting down this ExecutorService may manipulate * threads that the caller is not permitted to modify * because it does not hold {@link * java.lang.RuntimePermission}{@code ("modifyThread")}, * or the security manager's {@code checkAccess} method * denies access. */ void shutdown(); /** * Attempts to stop all actively executing tasks, halts the * processing of waiting tasks, and returns a list of the tasks * that were awaiting execution. * *This method does not wait for actively executing tasks to * terminate. Use {
@link #awaitTermination awaitTermination} to * do that. * *There are no guarantees beyond best-effort attempts to stop * processing actively executing tasks. For example, typical * implementations will cancel via {
@link Thread#interrupt}, so any * task that fails to respond to interrupts may never terminate. * * @return list of tasks that never commenced execution * @throws SecurityException if a security manager exists and * shutting down this ExecutorService may manipulate * threads that the caller is not permitted to modify * because it does not hold {@link * java.lang.RuntimePermission}{@code ("modifyThread")}, * or the security manager's {@code checkAccess} method * denies access. */ List* If you would like to immediately block waiting * for a task, you can use constructions of the form * {
@code result = exec.submit(aCallable).get();} * *Note: The {
@link Executors} class includes a set of methods * that can convert some other common closure-like objects, * for example, {@link java.security.PrivilegedAction} to * {@link Callable} form so they can be submitted. * * @param task the task to submit * @param继承了Executor接口,提供了对于任务执行生命周期的管理方法的接口。
共有3种状态:运行、关闭和已终止
shutdown方法:
- 启动平缓关闭过程
- 不再接受新任务
- 等待正在执行的任务完成
- 等在已提交但是未执行的任务完成
- 关闭Executor
- 该方法并不会阻塞等待shutdown过程完全结束,如果需要阻塞,需要额外调用awaitTermination方法
showdownNow方法:强制取消正在运行的任务并且不再开启新任务
不要使用Timer类,该类早已过期,Try ScheduledExecutorService和DelayQueue的组合
Callable与Future
想到一道面试题,如何用java加载一个大页面,包括文字图像,音频视频等。
Executor的submit方法返回的都是future对象,对于长时间任务,或者I/O开销大的任务,可以提交一个实现Callable接口的task,并在需要时才调用future对象的get方法查看执行结果。
这里书中提到:异构任务的并发,还是取决于慢的那个异构任务有多慢,真正能发挥并发特性的是,大量同构且相互独立的任务并发处理。(抢票啊,秒杀啊,巴拉巴拉)
CompletionServiceExecutor:
Executor+BlockingQueue, 其实现就是将callable对象在提交时包装在一个扩展的futuretask对象内,该对象复写了futuretask的done方法,在任务完成的时候会把future对象加入到一个blockingqueue中。使用方可以通过poll和take等队列操作获取队列中已经完成的future任务结果。
简单说一下poll和take,看源码注释,获取队头对象,并删除,poll不会阻塞,没有就返回null,take会,poll还有一个带timeout参数的方法。
TODO:ExecutorService实现类的源码解析
第7章 取消与关闭
本章内容较为底层,可能是个人对于中断的理解有所欠缺,通读一遍下来不太有收获,之后准备再去复习一下“中断”的概念再来重看一遍。
任何代码都可能抛出一个RuntimeException。每当调用另一个方法时,都要对它的行为保持怀疑,不要盲目地认为它一定会正常返回,或者一定会抛出在方法声明中声明的某个已检查异常。对调用的代码越不熟悉,就越应该对其代码行为保持怀疑。
守护线程
在JVM启动时创建的所有线程中,除了主线程以外,其他都是守护线程,包括GC等。当创建一个新线程时,会继承创建者的守护状态,因此,主线程创建的线程都是普通线程。
“当一个线程退出时,JVM会检查其他正在运行的线程,如果这些线程都是守护线程,那么JVM会正常退出操作。” 这句话完全没有读懂,我感觉这一整章都翻译的不太好,mark一下之后去看一下英文原版是怎么描述的。
JVM停止时,所有守护线程将被直接抛弃,所以不要在守护线程里持有一些需要释放清理的资源。
本章最后说到避免使用finalize方法,这一点在JVM虚拟机那本书里也提到了,事实上现在大家都是这么做的--在finally中调用各种close,release,teardown方法进行收尾清理,关闭连接,归还文件句柄/套接字句柄等。
第8章 线程池的使用
线程池的大小
想要正确地设置线程池的大小,必须分析计算环境、资源预算和任务的特性。
- 多少个CPU?
- 多大内存?
- 计算密集型 还是 I/O密集型 还是两者都有?
- 是否需要数据库连接?
对于计算密集型的任务,在拥有Ncpu个处理器的系统上,当线程池大小为Ncpu+1时,通常能实现最优的利用率。这个额外的线程可以确保,其他线程由于某些原因暂停时,可以利用到空出来的CPU时钟周期。
对于包含I/O操作或者其他阻塞操作的任务,由于线程不会一直执行,因此线程池的规模应该大一些。要正确的设置线程池的大小,需要估算出任务的等待时间与计算时间的比值。
一种比较简单的调节线程池大小的方式是:在某个基准负载下,分别设置不同大小的线程池来运行应用程序,观察CPU的利用率水平。
书上给出的公式:要使处理器达到期望的使用率,线程池的最优大小为:
Nthreads=Ncpu*Ucpu*(1+W/C)
其中,Ucpu是期望的CPU利用率,W是等待时间,C是计算时间。
而对于内存等其他资源,可以通过以下方式计算线程池的大小约束:
先计算每个任务对该资源的需求量,然后用该资源的可用总量除以每个任务的需求量,得出的就是线程池大小的上限。
队列任务
当请求的到达速率超过了服务器的处理速率,请求就会堆积起来,ThreadPoolExecutor的应对措施是允许通过一个BlockingQueue来保存等待执行的任务。
其中newFixedThreadPool在默认情况下使用的一个无上界队列,LinkedBlockingQueue,如果所有工作者线程都处于忙碌状态,那么任务将在队列中等候,并且如果处理速度一直跟不上,队列将无限制的增加。
当然,跟稳妥的方式是使用有界队列,例如有界的LinkedBlockingQueue,PriorityBlockingQueue等,加上适当的饱和策略(队列满了之后如何处理后续来的请求)。
饱和策略
ThreadPoolExecutor的饱和策略可以通过调用setRejectedHandler来修改
- AbortPolicy:抛出异常,让调用者自己处理
- DiscardPolicy:悄悄的直接抛弃任务
- DiscardOldest:抛弃队列头部的任务(或者优先级最高的),尝试提交新的任务。
- Caller-runs:是一种调节机制,该策略不抛弃任务也不扔出异常,而是将任务退回给调用者的主线程执行,从而使得主线程不能accpet新任务,新任务将会堆积在TCP层面,如果TCP层满了,会自行抛弃请求,该策略实现了一种平缓的降低性能机制。
可以通过扩展ThreadPoolExecutor(继承),实现beforeExecute,afterExecute(如果是计算任务时间的话,before和after可以通过threadlocal变量来通信)和terminate等方法,来对任务的执行添加日志监控和统计信息收集。
第9章 图形用户界面应用程序
由于GUI界面比较过气了,这章大家可以快速通读一下,看看有没有什么值得借鉴的设计思想和方法论,然后进入第三部分:活跃性、性能与测试