线程池核心知识点(通俗易懂 简单白话)
线程池的好处
-
降低资源消耗: 不用线程池的话,线程的创建和销毁都要消耗资源;而线程池的复用可以避免这种资源消耗。 提高响应速度: 不用线程池的话,当任务到达时,需要先创建一个线程才能执行;而线程池中任务则可以不需要等到线程创建就能立即执行。 提高线程的可管理性: 不用线程池的话,大量线程难以管理;而线程池可以进行统一的分配,调优和监控。
用Executors创建线程池
用 Executors 创建的常见线程池类型:
-
newCachedThreadPool: 线程数量可变的线程池,空闲线程空闲久了(默认一分钟)就会终止。 newFixedThreadPool: 线程数量固定的线程池,最经典的线程池,如果所有线程都不空闲,新任务会被放入任务队列,排队等有空闲线程再被执行。 newSingleThreadExecutor: 只有一个线程的线程池,任务会被保存在任务队列中,排队被执行。 newScheduleThreadExecutor: 线程数量固定的线程池,用于执行定时任务。
然而,这几种线程池有可能产生 OOM 问题。 newCachedThreadPool 的线程数最大可达 Integer.MAX_VALUE,newFixedThreadPool 和 newSingleThreadExecutor 的任务队列长度最大可达 Integer.MAX_VALUE。 所以一般都用 ThreadPoolExecutor。
用ThreadPoolExecutor创建线程池
ThreadPoolExecutor 线程池的 7 个核心参数:
-
corePoolSize: 核心线程数,只要线程池还在,就始终至少有这么多线程活着。 maximumPoolSize: 最大线程数,如果任务队列满了,可同时运行的线程数就会变为这个最大线程数,拉满了跑! keepAliveTime: 非核心线程的心跳时间,如果非核心线程在这么长的时间里一直空闲,就会死亡。 unit: 心跳时间 keepAliveTime 的计量单位。 workQueue: 任务队列,用来存现在没有空闲线程给他执行的新任务。
以上 5 个是创建时必须传入的,ThreadPoolExecutor 的4 个不同构造器都需要传入这 5 个参数。 下面 2 个创建时可以不传入,但是其实构造器会用默认的来创建,所以其实 7 个参数都必须有。
-
threadFactory: 线程工厂,线性池中的线程是用工厂模式创建的。 handler: 饱和策略,也就是满了(所有线程都在工作,且任务队列也满了)的时候该怎么办。 AbortPolicy(默认): 抛异常,完事。 DiscardPolicy: 装没看见,直接把装不下的新任务扔了。 DiscardOldestPolicy: 新的顶走最老的,把队列里最老的任务(workQueue 的队首任务)扔了,放新的进去。 CallerRunsPolicy: 不接活,谁给我的这个任务就让谁执行,让调用 execute() 方法的线程执行装不下的任务。
线程池常用的阻塞队列
-
LinkedBlockingQueue: 用于 FixedThreadPool 和 SingleThreadExecutor,这两种线程池总是有很多任务在等待,所以LinkedBlockingQueue的容量为 Integer.MAX_VALUE。 SynchronousQueue: 用于 CachedThreadPool,这种线程池的线程容量为 Integer.MAX_VALUE,不会有很多任务在等待,所以 SynchronousQueue 的容量有限,并且使用的是生产者-消费者的模式。如果生产者生产了产品没有消费者去消费,生产者就会进入阻塞。 DelayedWorkQueue: 用于 ScheduleThreadPool 和 SingleThreadScheduleExecutor,这两种线程池用于执行定时任务,所以 DelayedWorkQueue 的存储结构用的是堆,从而把任务按执行时间排序。
线程池的执行原理
- 核心线程还有没有空闲的?有就直接用,没有就下一步。
- 任务队列满了没?没满就让新任务进去等着,满了就下一步。
- 整个线程池满了没?没满就创建一个新线程,满了就按 handler 饱和策略来办。
可以发现,第二步说明线程池是很不想创建非核心线程的,就想着能只用核心线程就把所有事办了,非得等到等待的任务都装不下了,才肯创建非核心线程。
下一篇:
Java开发已经烂大街,没前途了?假的