在任务与执行策略之间的隐形耦合
线程饥饿死锁
只要线程池中的任务需要无限期的等待一些必须由池中其他任务才能提供的资源或条件, 例如某个任务等待另一个任务的返回值或执行结果, 那么除非线程池足够大, 否则将发生线程饥饿死锁(Thread Starvation DeadLock)
运行时间较长的任务
- 执行较长时间的任务不仅会造成线程池阻塞, 甚至还会增加执行时间较短任务的服务时间.
- 如果线程池中线程的数量远小于稳定状态下执行时间较长任务的数量, 那么到最后可能所有的线程都会执行这些执行时间较长的任务, 从而影响整体的响应性
- 如果线程池中总是充满了被阻塞的任务, 那么也可能表明线程池的规模过小.
有一项技术可以缓解执行时间较长任务造成的影响, 就是限定任务等待资源的时间, 而不要无限制的等待.
设置线程池的大小
- 过大, 资源竞争激烈, 导致更高的内存使用甚至耗尽资源
- 过小, 吞吐率低
计算密集型
Nthreads=Ncpu+1
- 保证线程偶尔由于页缺失故障或者其他原因而暂停时, 这个额外的线程也能确保CPU的时钟周期不会被浪费
I/O密集型
配置ThreadPoolExecutor
线程的创建与销毁
- corePoolSize, 基本大小即线程池的目标大小, 即没有任务执行时线程池的大小, 且只有在工作队列满了的情况下才会创建超出这个数量的线程
- maximumPoolSize, 线程池的最大大小表示可同时活动的线程数量上限
- 如果某个线程的空闲时间超过了存活时间keepAliveTime, 那么将被标记为可回收的, 并且当线程池的当前大小超过了基本大小时, 这个线程将被终止
管理队列任务
ThreadPoolExecutor允许提供一个BlockingQueue来保存等待执行的任务, 基本的任务排队方法有3种:
- 无界队列, newFixedThreadPool和newSingleThreadExecutor在默认情况下将使用一个无界的LinkedBlockingQueue
- 有界队列, 如ArrayBlockingQueue, 有界的LinkedBlockingQueue, PriorityBlockingQueue
- 同步移交(Synchronous Handoff), 对于非常大的或者无界的线程池, 可以使用SynchronousQueue来避免任务排队, newCachedThreadPool工厂方法中就使用了SynchronousQueue
SynchronousQueue不是一个真正的队列, 而是一种在线程之间进行移交的机制, 要将一个元素放入SynchronousQueue中, 必须有另一个线程正在等待接收这个元素, 如果没有线程正在等待, 并且线程池的大小小于最大值, 那么ThreadPoolExecutor将创建一个新的线程, 否则根据饱和策略, 这个任务将被拒绝
- 只有当任务相互独立时, 为线程池或工作队列设置界限才是合理的
- 如果任务之间存在依赖性, 那么有界的线程池或队列可能导致线程饥饿死锁问题, 此时应该使用无界的线程池, 例如newCachedThreadPool
饱和策略
- AbortPolicy, 中止策略(默认), 抛出RejectedExecutionException
- DiscardPolicy, 抛弃策略, 抛弃该任务
- DiscardOldestPolicy, 抛弃最旧的策略, 抛弃下一个将被执行的任务, 然后尝试提交新的任务(不要与优先级队列一起使用, 不然会抛弃掉优先级最高的)
- CallerRunsPolicy, 调用者运行策略, 将某些任务回退到调用者, 从而降低新任务的流量
(将下一个任务给到调用execute的主线程运行, 主线程运行任务需要一定时间, 因此至少在一段时间内不能提交任务, 在这期间主线程不会调用accept, 因此请求将保存在TCP层的队列中, 如果持续过载, TCP层队列被填满也会开始抛弃请求, 即过载情况会逐渐向外蔓延, 从线程池到队列到应用程序再到TCP层, 最终达到客户端, 导致服务器在高负载下实现一种平缓的性能降低)
这个例子看不太懂, 先留着吧
自定义线程工厂
拓展ThreadPoolExecutor
ThreadPoolExecutor提供了几个子类中可以改写的方法如beforeExecute, afterExecute和terminated(可以用于释放资源, 发送通知或者手机finalize统计信息等操作), 可以进行拓展以添加日志, 计时, 监视或者统计信息收集等功能
- beforeExecute抛RuntimeExeception时, 任务不会被执行, 并且afterExecute不会被调用
- 任务从run中正常返回或抛异常返回, afterExecute都会被调用, 如果任务在完成后带有一个Error, 那么afterExecute就不会调用
- 在线程池完成关闭操作时会调用terminate, 也就是再所有任务都已经完成并且所有工作者线程也已经关闭后