Java 并发 线程池
时间:2019/3/29 18:22:50
参考:
线程池#
线程池可以重用已经创建的线程,从而减少线程创建的开销,提高任务执行效率。
Jdk1.5 提供了 ThreadPoolExecutor,是一个直观的线程池模型。
Jdk1.7 引入了 ForkJoinPool,核心方法是 fork 分解 和 join 合并,简化 分解/合并 模型的任务的执行方式。用于任务需要拆分成子任务,或任务依赖多个子任务的场景。
ThreadPoolExecutor 线程池#
在 Java 中线程池基于 ThreadPoolExecutor 类实现。可以用过不同的参数配置不同的线程池。
参数:
int corePoolSize:线程池大小。int maximumPoolSize:最大线程池大小。long keepAliveTime:线程执行结束之后保留时间,需要指定时间单位。BlockingQueue workQueue: 阻塞队列,使用不同的阻塞队列可以实现不同的任务执行策略。ThreadFactory threadFactory:线程创建工厂。
corePoolSize 用于指定线程中保留线程的数量,即空闲时线程池中保留的线程数量。maximumPoolSize 用于指定线程池最大线程数量。在 (corePoolSize,maximumPoolSize] 之间的线程用完之后会被丢弃,在 [1,corePoolSize] 之间的线程用完之后被放进线程池。线程池中的线程空闲时间超过 keepAliveTime 会从线程池中移除。 阻塞队列用于控制线程池执行任务的策略,不同的阻塞队列可以达到不同的效果。线程工厂用于创建线程。
线程数量:
当 corePoolSize 等于 maximumPoolSize 时可以做固定大小的线程池。
阻塞队列
LinkedBlockingQueue:可以做有界和无界队列使用。ArrayBlockingQueue:可以做有界和无界队列使用。PriorityQueue:优先级队列,无界。SynchronousQueue:数据转换队列,本身不缓存数据,消息入队之后保持阻塞直到有消费者消费,因此中间并不缓存数据。
内存溢出问题:
使用无界队列时,当新任务创建任务速度,远远大于任务执行速度,会造成任务堆积,随着任务越来越多会造成内存溢出。
使用数据转换队列,只要有新任务到达,且此时没有空闲线程,就会创建新线程,当创建线程数量过多时也会造成内存溢出。
内存溢出问题解决方案:
使用有界队列,当阻塞的任务数量达到队列的容量限制之后,通过执行相应的策略 RejectedExecutionHandler ,从而避免内存溢出。
ThreadPoolExecutor.AbortPolicy:拒绝接收任务,并抛出运行时异常RejectedExecutionException。ThreadPoolExecutor.DiscardPolicy:丢弃新到来的任务。ThreadPoolExecutor.DiscardOldestPolicy:丢弃旧的未执行的任务。ThreadPoolExecutor.CallerRunsPolicy:在提交任务的线程上执行任务。可达到阻塞主线程的效果,当任务量过大时阻塞主线程,使得主线程可以据此来丢弃或者暂存其它任务。
扩展方法:
可以用于任务及线程池的数据统计,以及日志记录等。
beforeExecute:任务执行前调用。afterExecute:任务执行结束之后调用。terminated:线程池终止之后调用。
默认线程池:
在Java中 Executors 提供了几种线程池的默认实现。
Executors.newFixedTheadPoll:执行任务的线程数量固定,新的任务会被放在阻塞队列中,队列的大小没有限制,当任务到达的速度远远大于执行速度时,阻塞队列存储的任务数量就会不断变大,直至内存溢出。Executors.newCachedTheadPoll:为每一个任务创建或使用一个线程,内部使用SynchronousQueue。Executors.newSingledThreadPool:每次执行一个任务,当新任务到达时会阻塞。
ForkJoinPool#
ForkJoinPool 构造函数CPU核心数确定线程的数量,适用于计算密集型的任务,不适用于IO密集的任务。
提供 Fork/Join 任务模型,适用于递归、分治、归并的场景。
Jdk1.8异步执行器#
CompletableFuture 是Jdk1.8新提供的异步执行器,提供 combine 和 compose
具体Demo可参考:c_future