07线程池的使用

线程池的使用

任务提交和执行策略之前的隐性耦合

线程池中任务提交需要指定执行策略的场景(隐性耦合):

  1. 依赖性任务:有依赖就会出现环,耦合①执行策略不得不顺序执行;当线程池不够大,可能出现依赖一个等待中的任务,耦合②线程池大小足够大
  2. 使用线程封闭的任务:在单线程的Executor中执行,任务可以不是线程安全的,但是一旦提交到线程池时,就会失去线程安全,耦合③单线程中执行
  3. 对响应时间敏感的任务:线程池中的线程都被长时间任务占有,导致其余任务响应变长,耦合④线程池不能太小(应该超过有较长执行时间的任务数量)
  4. 使用ThreadLocal的任务:线程会重复使用,耦合⑤在线程池中使用ThreadLocal,要考虑到相应的清理工作。

线程饥饿死锁

依赖性任务如何导致线程饥饿死锁?

已执行的任务等待一直未能执行的任务

如果每个任务都需要使用有限的资源(如数据库连接),那么不管线程池多大,都会表现出和和连接资源相同的大小 

运行时间较长的任务

会导致什么问题?

线程池中的线程都被长时间任务占有,导致其余任务响应变长,线程池不能太小。应该超过有较长执行时间的任务数量

线程池大小的设置

线程过大过小都会有问题:

  • 过大,那么大量的线程将在相对很少的CPU和内存资源上发生竞争,这不仅会导致更高的内存使用量,而且还可能耗尽资源
  • 过小,那么将导致许多空闲的处理器无法执行工作,从而降低吞吐量

合适线程池大小可能要考虑的因素?

cpu数量,内存大小,系统类型(IO密集,计算密集),是否需要优先的资源(如连接池),任务执行的时间

灵光一现:IO多路复用到底有什么优势?

对于IO密集型(文件、网络),理论上线程是越多越好,但是由于线程也会带来消耗,且一般而言,一个应用的线程是有上限的,因此出现了其它各种解决方案来解决线程带来的消耗,如IO多路复用、协程

对于计算密集型,太多线程并不能带来性能上的提升,一般设置为核心线程数+1,执行算法才是关键。

关于NIO,想了解更多可以查看 NIO

有哪些计算方式?

一般计算公式:

N(threads) = N(cpu)U(cpu)\(1+W/C)

N(cpu) = CPU的数量 = Runtime.getRuntime().availableProcessors();

U(cpu) = 期望CPU的使用率,0<=U(cpu)<=1 ;

W/C = 等待时间与运行时间的比率

另外,还存在其他因素,如优先资源的数量(和资源相同的大小)

配置ThreadPoolExceutor

1
2
3
4
5
6
7
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
  • CorePoolSize: 线程池基本大小,在创建ThreadPoolExecutor初期,线程并不会立即启动,而是等到有任务提交时才会启动,除非调用prestartAllCoreThreads,并且只有在工作队列满了的情况下才会创建超出这个数量的线程
  • MaxmumPooSize: 线程池最大大小表示可同时活动的线程数量的上限。
  • keepAliveTime:若某个线程的空闲时间超过了keepAliveTime, 则被标记为可回收的
  • workQueue:用于保存超过线程池线程处理速率的Runnable任务的队列 (三种:无界队列、有界队列和同步移交)
  • handler:饱和策略,当队列满时如何处理溢出的任务(终止,抛弃,抛弃最旧,调用者执行)
  • 线程工厂:定制线程的特性(如设置线程名,自定义UncaughtThreadHandler异常处理程序,日志,统计,控制访问权限)

有哪些默认配置池?

  • newFixedThreadPool: CorePoolSize = MaxmumPoolSize
  • newCachedThreadPool: CorePoolSize=0,MaxmumPoolSize=Integer.MAX_VALUE,线程池可被无限扩展,需求降低时自动回收
  • ForkJoinPool(1.7):使用分治和工作密取的思想,高效的完成,详情可以查考 ForkJoinPool

线程池扩展

ThreadPoolExecutor使用了模板方法模式,提供了beforeExecute、afterExecute和terminated扩展方法:

  • 线程执行前调用beforeExecute(如果beforeExecute抛出了一个RuntimeException,那么任务将不会被执行)
  • 线程执行后调用afterExecute(抛出异常也会调用,如果任务在完成后带有一个Error,那么就不会调用afterExecute)
  • 在线程池完成关闭操作时调用terminated,也就是所有任务都已经完成并且所有工作者线程也已经关闭后

ThreadPoolExecutor生命周期:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
>   //ThreadPoolExecutor
> * The runState provides the main lifecycle control, taking on values:
> *
> * RUNNING: Accept new tasks and process queued tasks
> * SHUTDOWN: Don't accept new tasks, but process queued tasks
> * STOP: Don't accept new tasks, don't process queued tasks,
> * and interrupt in-progress tasks
> * TIDYING: All tasks have terminated, workerCount is zero,
> * the thread transitioning to state TIDYING
> * will run the terminated() hook method
> * TERMINATED: terminated() has completed
> *
> * The numerical order among these values matters, to allow
> * ordered comparisons. The runState monotonically increases over
> * time, but need not hit each state. The transitions are:
> *
> * RUNNING -> SHUTDOWN
> * On invocation of shutdown(), perhaps implicitly in finalize()
> * (RUNNING or SHUTDOWN) -> STOP
> * On invocation of shutdownNow()
> * SHUTDOWN -> TIDYING
> * When both queue and pool are empty
> * STOP -> TIDYING
> * When pool is empty
> * TIDYING -> TERMINATED
> * When the terminated() hook method has completed
> * // runState is stored in the high-order bits
> * private static final int RUNNING = -1 << COUNT_BITS;
> * private static final int SHUTDOWN = 0 << COUNT_BITS;
> * private static final int STOP = 1 << COUNT_BITS;
> * private static final int TIDYING = 2 << COUNT_BITS;
> * private static final int TERMINATED = 3 << COUNT_BITS;
>

递归算法的并行优化

  • 如果循环中的迭代操作都是独立的,并且不需要等待所有的迭代操作都完成再继续执行,那么就可以使用Executor将串行循环转化为并行循环
  • 如果需要提交一个任务集并等待它们完成,那么可以使用ExecutorService.invokeAll
  • 如果递归执行的任务中,在每个迭代操作中都不需要来自于后续递归迭代的结果,可以创建一个特定于遍历过程的Executor,并使用shutdown和awaitTermination等方法,等待上面并行运行的结果

本文标题:07线程池的使用

文章作者:Sun

发布时间:2020年08月25日 - 18:08

最后更新:2020年08月25日 - 18:08

原始链接:https://sunyi720.github.io/2020/08/25/Java并发编程/07线程池的使用/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。