八股整理:说说线程池的工作原理?

线程池的底层是基于线程和任务队列来实现的,创建线程池的创建方式通常有以下两种:

  1. 普通 Java 项目,使用 ThreadPoolExecutor 来创建线程池,这点《阿里巴巴Java开发手册》中也有说明,如下图所示:

alt

  1. Spring 项目中,会使用代码可读性更高的 ThreadPoolTaskExecutor 来创建线程池,虽然它的底层也是通过 ThreadPoolExecutor 来实现的,但 ThreadPoolTaskExecutor 可读性更高,因为它不需要在构造方法中设置参数,而是通过属性设置的方式来设置参数的,所以可读性更高。

Spring 内置的线程池 ThreadPoolTaskExecutor 的使用示例如下:

@Configuration
public class AsyncConfig {
    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数
        executor.setCorePoolSize(5);
        // 最大线程数
        executor.setMaxPoolSize(10);
        // 队列容量
        executor.setQueueCapacity(20);
        // 线程池维护线程所允许的空闲时间
        executor.setKeepAliveSeconds(60);
        // 线程池对拒绝任务(无线程可用)的处理策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 初始化
        executor.initialize();
        return executor;
    }
}

1.线程池工作流程

当有任务来了之后,线程池的执行流程是这样的:

  1. 先判断当前线程数是否大于核心线程数,如果结果为 false,则新建线程并执行任务。
  2. 如果大于核心线程数,则判断任务队列是否已满,如果结果为 false,则把任务添加到任务队列中等待线程执行。
  3. 如果任务队列已满,则判断当前线程数量是否超过最大线程数,如果结果为 false,则新建线程执行此任务。
  4. 如果超过最大线程数,则将执行线程池的拒绝策略。

如下图所示:

alt

2.拒绝策略

当线程池无法接受新任务时,会触发拒绝策略,内置的拒绝策略有四种:

  1. AbortPolicy:默认策略,直接抛出 RejectedExecutionException 异常。
  2. CallerRunsPolicy:由调用者线程执行任务。
  3. DiscardPolicy:默默地丢弃任务,没有任何异常抛出。
  4. DiscardOldestPolicy:尝试抛弃队列中最旧的任务,然后重新尝试提交当前任务。

除了内置的拒绝策略之外,我们还可以设置自定义拒绝策略,它的实现如下:

import java.util.concurrent.RejectedExecutionHandler;  
import java.util.concurrent.ThreadPoolExecutor;  
  
public class CustomRejectedExecutionHandler implements RejectedExecutionHandler {  
  
    @Override  
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {  
        // 在这里处理拒绝的任务  
        System.err.println("任务被拒绝执行: " + r.toString());  
        // 可以选择记录日志、抛出自定义异常或采取其他措施  
        // 例如,可以将任务保存到某个队列中,稍后再尝试重新执行  
    }  
}

使用自定义拒绝策略:

import java.util.concurrent.ArrayBlockingQueue;  
import java.util.concurrent.ThreadPoolExecutor;  
import java.util.concurrent.TimeUnit;  
  
public class ThreadPoolDemo {  
  
    public static void main(String[] args) {  
        // 配置线程池参数  
        int corePoolSize = 5;  
        int maximumPoolSize = 10;  
        long keepAliveTime = 60L;  
        TimeUnit unit = TimeUnit.SECONDS;  
        int queueCapacity = 25;  
  
        // 创建一个阻塞队列  
        ArrayBlockingQueue<Runnable> workQueue = 
            new ArrayBlockingQueue<>(queueCapacity);  
  
        // 创建 ThreadPoolExecutor 实例  
        ThreadPoolExecutor executor = new ThreadPoolExecutor(  
                corePoolSize,  
                maximumPoolSize,  
                keepAliveTime,  
                unit,  
                workQueue,  
                new CustomRejectedExecutionHandler() // 使用自定义的拒绝策略  
        );  
  
        // 提交任务  
        for (int i = 0; i < 50; i++) {  
            final int taskId = i;  
            executor.execute(() -> {  
                System.out.println("执行任务: " + taskId + " 由线程 " + Thread.currentThread().getName() + " 执行");  
                try {  
                    Thread.sleep(1000); // 模拟耗时任务  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            });  
        }  
  
        // 关闭线程池(这不会立即停止所有正在执行的任务)  
        executor.shutdown();  
    }  
}

课后反思

实际项目中线程池会使用哪种拒绝策略?为什么?线程池是通过什么机制来创建线程的?线程池创建线程时可以设置哪些属性?

参考 & 鸣谢

javacn.site

#八股文##java#
全部评论
好!被问到2次了都不知道怎么答,现在了解了
点赞 回复
分享
发布于 03-11 17:31 湖北

相关推荐

部门:质量效能&nbsp;&nbsp;&nbsp;&nbsp;base:上海&nbsp;&nbsp;时间:4.15&nbsp;八股盛宴,总时长80分钟。。。 面试的时候忘记录音了,只能简单回忆一下被问到的问题,如下所示:1、自我介绍。2、简历上都是开发相关的东西,于是被问到为什么投测开。3、手撕:二叉树中所有从根节点到叶子节点路径的和。4、对java泛型有了解吗,为什么要用泛型,泛型类能否直接被使用。5、AOP有了解过吗,你项目中有没有使用过,他底层是怎么实现的。6、静态代理和动态代理有什么区别。7、如果定义了多个切入点表达式,怎么保证执行的顺序。8、Springboot中starter的作用是什么。9、有哪些注解可以用来声明一个bean。10、@bean和@component注解有什么区别。11、Springboot加载配置有哪些方式。12、说一下bean的生命周期。13、说一下bean生命周期中aware接口是用来干什么的。14、springboot中的bean是线程安全的吗。15、spring的事务你有用到吗,有哪些实现方式。16、用注解声明式事务,默认采用的是什么事务管理器,有其他选择吗。17、spring的动态代理默认采用的是什么。JDK代理和CGLIB代理有什么区别。18、redis有用到过吗,redis缓存穿透、缓存击穿、缓存雪崩问题及其解决方案。19、redis持久化方法,两者有什么区别以及使用场景。20、redis怎么保证高并发和高可用的。21、redis的基本数据类型。然后给了一些场景,问我用什么数据类型&nbsp;比如:排行榜。22、mysql的三大日志有了解过吗,分别说一下。23、mysql事务隔离级别,redolog保证了什么,undolog保证了什么。24、MVCC是怎么实现的。25、mysql为什么要用b+树作为索引结构。26、java线程池的核心参数有哪些,线程工厂的作用是什么,有哪些拒绝策略。27、任务队列你会采用有界队列还是无界队列。28、解释一下AQS是什么,用来干什么的。29、说一下原子类有哪些。30、Java常见的并发容器。31、ThreadLocal在使用中会出现什么问题。32、消息队列任务堆积问题可能的原因是什么。 暂时只记得这么多。。。反问:1、质量效能部门负责什么&nbsp;2、测开需要进一步学习什么&nbsp;3、多久出结果
点赞 评论 收藏
转发
1 12 评论
分享
牛客网
牛客企业服务