为什么不使用Executors创建线程池
使用Executors线程池创建线程会有资源耗尽的风险
- 创建FixedThreadPool 或者 SingleThreadPool 时 , 默认创建的请求队列的长度为Integer.MAX_VALUE , 可能会导致请求堆积 , 造成内存溢出
- 创建CachedThreadPool , 默认最大线程数量是Integer.MAX_VALUE , 可能会导致大量线程的创建 , 造成内存溢出
- 综上 , 为了防止资源耗尽 , 不推荐使用Executors创建线程池
线程池的工作流程
- 判断当前线程数是否大于核心线程数 , 如果不大于 , 则创建新线程执行任务
- 如果当前线程数大于核心线程数 , 则判断当前任务队列是否已经满了 , 如果没满 , 就将任务提交给任务队列
- 如果任务队列满了 , 则判断当前线程数是否大于最大线程数 , 如果不大于 , 就创建临时线程执行该任务
- 如果当前线程数大于最大线程数 , 那么执行线程池的拒绝策略
线程池的状态
RUNNING:运行状态 . 线程池创建之后就处于RUNNING状态 , 此时可以接收/处理新任务 ,
SHUTDOWN:关闭状态 . 调用shutdow方法后就处于SHUTDOWN状态 , 不能接收新任务可以处理未完成任务
STOP:阻断状态 . 调用shutdownNow方法后处于STOP状态 , 不能接收新任务 , 不会完成未完成任务 , 同时会将正在进行的任务终止
TIDYING:整理状态 . 所有任务完成时 , 就会变为TIDYING状态 . 处于当前状态的线程池会调用terminate()方法
TERMINATED:终止状态 . 在 terminated() 方法执行完后进入该状态
线程池的关闭方法
- shutdown( ) 关闭当前线程池 , 调用该方法之后 , 线程池不再接收新的任务 , 但是会将没有执行完的任务继续执行
- shutdownNow( ) 立即关闭线程池 , 调用该方法后 , 线程池不再接收新的任务 , 同时没有完成的任务也不会继续执行 , 而是将没有完成的任务作为返回值返回
- isShutdown( ) 判断线程池是否关闭 , true 关闭 false 没关闭
- isTerminated( ) 判断线程池是否终止 , true 关闭 false 没关闭
问题1 : shutdown和shutdownNow的区别
- shutdownNow会首先将线程池的状态设置为STOP , 然后尝试停止所有正在执行任务或者没有执行任务的线程 (所有任务线程都停止 , 不管有没有在执行任务) ;
- shutdown会将线程池的状态设置为SHUTDOWN , 然后尝试停止所有没有执行任务的线程 ( 停止没有执行任务的线程 , 执行任务的线程不停止 ) ;
- 总结 : 所有shutdown会将没执行的任务执行完 , shutdownNow则是立即停止 , 没完成的任务也不执行了 .
问题2 : 调用shutdown后线程池会终止吗 ?
调用shutdown后 , 线程池不会立即终止 . 在关闭线程池之后 , 只有将没有完成的任务完成后 , 再对线程池申请的资源进行回收之后 , 线程池才会终结 (terminated)
怎么判断线程池任务执行完毕
- 判断线程池是否处于终结状态
优点 : 实现简单
缺点 : 需要关闭线程池 , 线程池状态才会变为terminated
public static void main4(String[] args) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 20,
0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1024));
addTask(threadPool);
if (isComplete(threadPool)) {
System.out.println("执行结束");
}
}
// 往线程池当中添加任务
public static void addTask(ExecutorService executorService) {
for (int i = 1; i <= taskSize; i++) {
final int runTaskSize = i;
executorService.submit(()->{
try {
System.out.println("第" + runTaskSize + "个任务正在执行" );
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
}
}
// 判断线程池任务是否执行结束
public static boolean isComplete (ThreadPoolExecutor threadPoolExecutor) {
// 判断线程池状态是否为终止状态
threadPoolExecutor.shutdown();
while (!threadPoolExecutor.isTerminated()) {
}
return true;
}
- 判断线程池任务数量和完成任务数量是否一致
优点 : 无需关闭线程池
缺点 : 由于线程池任务数量和完成任务数量是一直变化的 , 因此得到的只是近似值
public static void main4(String[] args) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 20,
0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1024));
addTask(threadPool);
if (isComplete(threadPool)) {
System.out.println("执行结束");
}
}
// 往线程池当中添加任务
public static void addTask(ExecutorService executorService) {
for (int i = 1; i <= taskSize; i++) {
final int runTaskSize = i;
executorService.submit(()->{
try {
System.out.println("第" + runTaskSize + "个任务正在执行" );
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
}
}
// 判断线程池任务是否执行结束
public static boolean isComplete (ThreadPoolExecutor threadPoolExecutor) {
// 判断线程池任务数量和完成任务数量是否一致
while (threadPoolExecutor.getTaskCount() != threadPoolExecutor.getCompletedTaskCount()) {
}
return true;
}
- 使用CountDownLatch计数器
原理 : 在底层创建一个计数器 , 计数器的初始值就是传入的任务数量 , 每次调用CountDown就让计数器减少1 , 当计数器的值为0之后就唤醒等待的线程
public static void addTaskCountLauch(ExecutorService executorService) throws InterruptedException {
// 创建计数器并指定任务数量
CountDownLatch countDownLatch = new CountDownLatch(taskSize);
for (int i = 1; i <= taskSize; i++) {
final int runTaskSize = i;
executorService.submit(()->{
try {
boolean shutdown1 = executorService.isShutdown();
System.out.println("第" + runTaskSize + "个任务正在执行" + "线程池状态" + shutdown1);
Thread.sleep(1000);
// 任务完成就减1
countDownLatch.countDown();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
}
// 当所有任务完成后就唤醒当前线程
countDownLatch.await();
System.out.println("执行结束");
}