intro:
如果不使用线程池,那么每个任务都是新开一个线程进行处理
--- drawback:
-
开销大,希望固定数量的线程依次执行,避免反复创建&销毁线程所带来的问题
-
过多的线程会占用太多内存
--- benefit:
-
用少量线程,避免内存占用过多
-
让线程都保持工作,并且反复执行任务,避免生命周期的损耗
线程池构造方法:
参数:
-
corePoolSize -- int -- 核心线程数;线程池初始化时,是没有任何线程的,在接收到任务后立即创建线程执行任务。线程通常会一直存活,除非exception
-
maxPoolSize -- int -- 当corePoolSize不足以完成任务时 i.e. corePoolSize=5,通常线程池中是有任务队列workQueue的,更多的任务会在任务队列中排序(尽可能不突破corePoolSize的数量),但是如果队列满了却还要新增任务,就要创建线程,最多创建到maxPoolSize的数量
-
keepAliveTime -- long -- 保持存活时间。线程数 > corePoolSize,并且多余的线程空闲的时间超过keepAliveTime,他们就会被终止。(超过 多久 回收多余线程)
-
workQueue -- BlockingQueue -- 任务存储队列 、工作队列
-
直接交换:SynchronousQueue -- 容量0,任务直接交换 任务量不是特别多,只是将任务通过队列进行中转
-
无界队列:LinkedBlockingQueue -- 队列很大,不会被放满
-
adv:适用于控制流量突增的情况
-
disadv:处理的速度跟不上任务提交的速度,会造成内存的浪费和OOM异常(内存溢出)
-
-
有界的队列: ArrayBlockingQueue -- 满了且不足maxPoolSize去创建线程
-
-
threadFactory -- 用来创建线程
-
或者默认创建线程方式 【Executors.defaultThreadFactory()】,默认创建出的线程都是在同一个线程组并且有相同的优先级5,非守护线程
-
如果自己指定ThreadFactory,那么可以改变线程名,线程组,优先级,是否是守护线程等。
-
-
Handler -- RejectedExecutionHandler -- 当线程数达到maxPoolSize,使用Handler进行拒绝
添加线程规则:
-
线程数 < corePoolSize,创建一个新线程来运行新任务。
-
线程数 >= corePoolSize && < maxPoolSize, 将任务放入队列中
-
队列满,线程数 < maxPoolSize, 创建一个新线程
-
队列满,线程数 >= maxPoolSize,则拒绝
线程池的创建
手动创建线程池更好,可以更加明确线程池的运行规则,避免资源耗尽的风险
FixedThreadPool:
在创建的过程中就固定了线程的数量,在执行的过程中,无论任务量的多少,是不会超过线程个数的
source code:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
corePoolSize 和maxPoolSize数量相同 导致固定线程数,使用的队列是LinkedBlockingQueue,队列很大不会被放满的
public class FixedThreadPoolTest {
public static void main(String[] args) {
// 固定线程数的线程池
ExecutorService executorService = Executors.newFixedThreadPool(4);
for(int i = 0; i < 1000; i++){
executorService.execute(new Task());
}
}
}
class Task implements Runnable{
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
output:
pool-1-thread-2
pool-1-thread-4
pool-1-thread-3
pool-1-thread-4
pool-1-thread-1
pool-1-thread-2
导致问题: OOM内存溢出异常 ---- OutOfMemory:
例子:
// 演示FixedThreadPool出错的情况出现OOM内存溢出
public class FixedThreadPoolOOM {
//尽量少的线程做事
private static ExecutorService executorService = Executors.newFixedThreadPool(1);
public static void main(String[] args) {
for(int i = 0; i < Integer.MAX_VALUE; i++){
executorService.execute(new SubThread());
}
}
}
class SubThread implements Runnable{
@Override
public void run() { // 任务时间尽量延长
try {
Thread.sleep(1000000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
SingleThreadExecutor
因为是单一线程运行执行任务,不需要传入任何参数
ExecutorService executorService = Executors.newSingleThreadExecutor();
source code:
corePoolSize 和 maxPoolSize 的数量一致,都是1,并且LinkedBlockingQueue 无限
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
缺点:请求堆积的时候,占用大量内存
CachedThreadPool
可缓存线程池,自动回收多余线程。 ---- 任务来了,就创建线程,任务结束,就回收线程
public class CachedThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
executorService.execute(new Task());
}
}
}
source code:
maxPoolSize设置为最大,无限创建线程,当任务特别多创建了很多线程的时候,就会导致OOM
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
ScheduledThreadPool
支持定时周期性的执行任务,可以根据时间做一些线程相关工作
public class ScheduledThreadPool {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService =
Executors.newScheduledThreadPool(10);
//周期性的 线程池支持schedule() -- 延迟5s执行任务
scheduledExecutorService.schedule(new Task(), 5, TimeUnit.SECONDS);
//初始延迟1s,之后每隔3s
scheduledExecutorService.scheduleAtFixedRate(new Task(), 1,3, TimeUnit.SECONDS);
}
}
source code:
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
new DelayedWorkQueue());
}
summary for normal auto-Thread pool
Parameter | FixedThreadPool | CachedThreadPool | ScheduledThreadPool | SingleThread |
---|---|---|---|---|
corePoolSize | constructor-arg | 0 | constructor-arg | 1 |
maxPoolSize | same as core | Integer.MAX_VALUE | Integer.MAX_VALUE | 1 |
keepAliveTime | 0s | 60s | 0s | 0s |
workingStealingPool:
1.8之后加入的,子任务,窃取
手动创建线程池:
-
线程数量设定:
-
CPU密集型,最佳线程数为CPU核心数1-2倍 i.e. 加密、计算hash等, -- 8 核,可以设置为8-16,因为CPU都是满负荷计算,分配给CPU就可
-
耗时IO型,最佳线程数一般会大于CPU核心数很多倍 i.e. 读写数据库,文件,网络,涉及到读取很多CPU都是处于等待状态不工作的
-
-
Brain Goetz 计算方法:
线程数 = CPU核心数*(1+平均等待时间/平均工作时间)
停止线程池:
shutdown()
虽然是关闭线程池,但不是立即的关闭,是将存量Task继续执行完,同时线程池就不再接受其他任务,抛出拒绝异常。RejectedExecutionException
public class ShutDown {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
executorService.execute(new ShutDownTask());
}
try {
Thread.sleep(1500);
executorService.shutdown(); // 关闭线程池,但非立即关闭
} catch (InterruptedException e) {
e.printStackTrace();
}
//shutdown不是立即的停止,但是如果shutdown后,再提交新的任务是会抛出异常的
executorService.execute(new ShutDownTask());
}
}
class ShutDownTask implements Runnable{
@Override
public void run() {
try {
Thread.sleep(500);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
output --- shutdown后提交任务,抛出异常
pool-1-thread-7
pool-1-thread-3
Exception in thread "main" java.util.concurrent.RejectedExecutionException
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:1768)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:767)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:658)
at pracThreadPool.ShutDown.main(ShutDown.java from InputFileObject:20)
pool-1-thread-4
pool-1-thread-8
isShutdown()
return boolean 判断是否进入停止状态,非判断是否已经停止
ExecutorServiceObject.isShutDown();
isTerminated()
return boolean 判断是否已经停止处理任务(完全终止)。 【vs isShutdown():isTerminated不是判断状态而是真正的停止】
awaitTermination(time,TimeUnit)
只有三种情况会有返回值return,返回前是Blocked状态。
-
所有任务都执行完毕
-
等待时间到了
-
等待时候被中断,会抛出interrupted exception
return boolean 用来测试在参数时间内,线程是否会完全终止的方法,作用:检测非关闭
shutdownNow()
立刻关闭。正在执行的线程被interrupted,队列中等待的任务会直接return,return List<Runnable>
public class ShutDown {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
executorService.execute(new ShutDownTask());
}
try {
Thread.sleep(1500);
List<Runnable> runnableList = executorService.shutdownNow();
// 合理利用runnableList,未执行的线程任务都存储在这个集合中。
System.out.println(runnableList.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ShutDownTask implements Runnable{
@Override
public void run() {
try {
Thread.sleep(500);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "被中断了");
}
}
}
线程池的拒绝
拒绝时机:
-
当Executor关闭,提交新任务会被拒绝抛出exception --- 已经关闭
-
当Executor对最大线程和工作队列容量使用有限边界并且已经饱和时 --- 能力不足
拒绝策略:
-
AboerPolicy:提交新任务,抛出异常exception
-
DiscardPolicy:提交新任务,直接忽略, 默默丢弃
-
DiscardOldestPolicy:丢弃最老的、存在时间最久的
-
CallerRunsPolicy:提交任务的线程执行,i.e. 主线程提交给线程池任务,但是线程池不接受,则交给主线程执行。针对提交线程,完成任务也是需要时间的,在完成之后才能再次向线程池提交任务,是更合理的。