From:Java并发编程的艺术
- 目录
BiBi - 并发编程 -0- 开篇
BiBi - 并发编程 -1- 挑战
BiBi - 并发编程 -2- volatile
BiBi - 并发编程 -3- 锁
BiBi - 并发编程 -4- 原子操作
BiBi - 并发编程 -5- Java内存模型
BiBi - 并发编程 -6- final关键字
BiBi - 并发编程 -7- DCL
BiBi - 并发编程 -8- 线程
BiBi - 并发编程 -9- ReentrantLock
BiBi - 并发编程 -10- 队列同步器
BiBi - 并发编程 -11- 并发容器
BiBi - 并发编程 -12- Fork/Join框架
BiBi - 并发编程 -13- 并发工具类
BiBi - 并发编程 -14- 线程池
BiBi - 并发编程 -15- Executor框架
线程池好处
1)降低资源消耗,避免重复的创建和销毁线程。
2)提高响应速度,不需要再等待线程,能够立刻执行。
线程池的处理流程
核心线程是否已满 > 队列是否已满 > 线程池是否已满
具体流程:
1)当前运行的线程少于核心线程数,则创建新线程来执行任务。【需要获取全局锁】
2)运行的线程等于或多于核心线程数,则将任务加入到等待队列中。
3)等待队列已满,并且已创建的线程数小于最大线程数,则创建新的线程来立刻执行该任务。【需要获取全局锁】【前提:等待队列有界】【建议使用有界队列,因为有预警能力】
4)超过最大线程数,则任务将被拒绝,并调用rejectedExecution()。
注意1:线程池的构建和使用应当尽可能避免获取全局锁。
注意2:当核心线程数没有满时,即使有空闲的线程能够执行新任务,也不用,还是要创建一个新线程去执行该任务。可以调用prestartAllCoreThreads()方法提前创建核心线程。
等待队列的选择
吞吐量由低到高排序:
ArrayBlockingQueue < LinkedBlockingQueue < SynchronousQueue【不存储元素】
Executors.newFixedThreadPool()和Executors.newSingleThreadExecutor()使用LinkedBlockingQueue【无界队列】。
Executors.newCachedThreadPool()使用SynchronousQueue。
Executors.newScheduledThreadPool()使用DelayQueue【无界队列】。DelayQueue中封装了PriorityQueue,按照任务的执行时间进行排序,如果时间相同则按照提交的序列号。
饱和策略 - RejectedExecutionHandler
当队列和线程池都满了,可以采用以下策略来执行新任务。
1)AbortPolicy 抛出异常【默认】
2)CallerRunsPolicy 只用调用者所在的线程来运行任务
3)DiscardOldestPolicy 丢弃队列里最近的一个任务,并执行当前任务
4)DiscardPolicy 不处理,直接丢弃
5)实现RejectedExecutionHandler接口,自定义处理操作。当需要记录日志或存储不能处理的线程时,可以在这里进行处理。
向线程池提交任务execute/submit
//不需要返回值【无法判断任务是否执行成功】
threadPool.execute(new Runnable() { });
//有返回值【通过返回值可以判断任务是否执行成功】
Future<Object> future = threadPool.submit(new Runnable() { });
Object t = future.get(); //阻塞当前线程,直到任务完成
关闭线程池
原理:遍历所有线程,调用线程的Interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。
threadPool.shutdown():将线程池状态设置成SHUTDOWN,然后中断所有没有正在执行的线程,
threadPool.shutdownNow():将线程池状态设置成STOP,然后停止所有正在执行或暂停的线程。
提示:shutdown或shutdownNow调用任意一个,isShutdown就为True。但只有当所有任务都关闭后,才表示线程池关闭成功,这时isTerminated才为True。
配置线程池
N代表设备CPU的个数。
1)CPU密集型任务
应尽可能少的分配线程,如:N+1【因为如果分配的多了,会消耗很大的CPU资源】
2)IO密集型任务
应尽可能多的分配线程,如:2*N【因为线程不是一直在执行】
3)混合类型任务
将其拆分成一个CPU密集型和IO密集型任务。
4)优先级不同任务
使用优先级队列PriorityBlockingQueue。
线程池的监控
可以继承线程池,重写线程池的beforeExecute、afterExecute、terminated【关闭】方法自定义一些处理。如:监控任务的平均时间,最大执行时间、最小执行时间等。
largestPoolSize表示线程池曾经创建过的最大线程数量,通过这个数可以判断线程池是否曾经满过。
异步任务池
背景:如果一个任务放到线程池之后,运行线程池的程序重启了,那么线程池里的线程就会丢失;线程池只能处理本机的任务,在集群环境中不能有效地调度所有机器的任务。
异步任务池的处理流程:每台机器会启动一个任务池,每个任务池里有多个线程池,当某台机器将一个任务交给任务池后,任务池会先将这个任务保存到数据库中,然后某台机器上的任务池会从数据库中获取执行的任务,再执行这个任务。
任务的状态包括:创建、执行中、重试、挂起、中止、完成。
任务池的任务隔离策略:使用不同的线程池处理不同的任务,或者不同的线程池处理不同优先级的任务。如果任务类型少,建议用任务类型来隔离;如果任务类型多,建议用优先级的方式来隔离。
异步任务池感觉有点分布式开发的意思。