关于线程池,面试的时候你时候还打怵,这里我有话要说保证让你对线程池的各个参数一边就懂

关于线程池,大家相信一定有所耳闻即使在日常工作中没有实际的应用,但是在面试过程中一定有被问到过。别说你没有面试过……

首先我们先简单的了解下线程池的大概含义:

线程池其实就是一种多线程处理形式,处理过程中将任务添加到队列中,然后再创建线程自动启动这些任务,这里要注意线程池中的所有线程都是后台线程,其实主要就是为了更好的处理任务,以及更高效的利用CPU。大致就是这些了;这里就不做更多的阐述了,可以自行百度百科一下哈。主要是这里也不是重点。你懂的🤪

重点来了。
面试的时候面试官一定这样问过你:

面试官🤓:请问你有了解过线程池吗?你可以简单聊一下嘛。

派大星🙋🏻‍♂️:
这个当然有了解过,线程池嘛,线程池可以看做是线程的集合。在没有任务时线程处于空闲状态,当请求到来:线程池给这个请求分配一个空闲的线程,任务完成后回到线程池中等待下次任务执行(而不是销毁)。这样就实现了线程的重用。这就是线程池的主要作用,以及线程池的存在的意义。

假设我们不使用线程池的时候,每当新来一个任务都需要开辟任务,请看如下的代码:

    public static void main(String[] args) throws Exception{
        ServerSocket serverSocket = new ServerSocket(83);
        try {
            while(true) {
                Socket socket = null;
                socket = serverSocket.accept();
                new Thread(socketServerThread).start();
            }
        } catch(Exception e) {
            log.error("Socket accept failed. Exception:{}", e.getMessage());
        } finally {
            if(serverSocket != null) {
                serverSocket.close();
            }
        }
    }

看的出来,每开创一个socket连接请求都会,新建一个线程Thread,按道理来说这时没有问题的哦,但是还是有一定的弊端的,比如:

每次创建线程,都会消耗时间有一定的系统资源的浪费,并且线程是有生命周期的,每次创建和销毁也是需要时间的。并且线程数也是占用系统CPU资源的,大量的空闲线程也会行用过多内存,给垃圾回收器带来压力,从而会造成 OutOfMemoryError 异常

面试官🤓:好的,你说的我大致了解了,那么其实你并有正面回答我的问题的。

派大星🙋🏻‍♂️:我了解,其实我上述说的就是为了引出线程池,因为我上面有提到,线程池会有各种的弊端从而导致 OutOfMemoryError 异常等,所以才推出了线程池。关于线程池的基本概念上面也大概讲述了,接下来我大致说一下JDK的线程池,看下图:

关于线程池,面试的时候你时候还打怵,这里我有话要说保证让你对线程池的各个参数一边就懂

JDK给我们提供的线程池API正如上图所示:

  • Executor:是基础
  • ExecutorService:提供了线程池生命周期的管理方法
  • AbstrzctExecutorServic:提供了默认实现
  • ThreadPoolExecutor:便是最常用的线程池
  • 图中的其余的两个便是定时执行的或者是延期执行的线程池,后续会给大家下一个简单的例子供大家了解。

面试官🤓:嗯,你对线程池的的基本信息掌握的还可以,那你能给我说一下,关于线程池的创建方式嘛?

派大星🙋🏻‍♂️:好的,其实实际中的线程创建方式是有很多的,但是我的理解主要分为两大类

  1. 利用Executors去创建,以及通过其衍生的多种方法。
  2. 便是利用ThreadPoolExecutor去创建。

关于以上的两种创建方式我们可以先简单的聊一下第一种通过 Executors以及其衍生方式创建的线程池。如图所示:

关于线程池,面试的时候你时候还打怵,这里我有话要说保证让你对线程池的各个参数一边就懂

以上大概分为4种分别是:

  1. newFixedThreadPool

创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待. 适用于任务量已知,相对耗时的任务

  1. newCachedThreadPool

核心线程数是0,最大线程数是Integer.MAX_VALUE,救急线程的空闲生存时间是60s,意味着全部是救急线程(60s后可以回收)救急线程可以无限创建.

  1. newSingleThreadExecutor

希望多个任务排队执行,线程数固定为1,任务数多于1时,会放入无界队列排队,任务执行完毕,这唯一的线程也不会被释放。

  1. newScheduledThreadPool

主要是用来执行定时任务

关于Executors及其衍生方式创建的线程池大致就介绍这么多,如果有需要了解更多关于线程池的笔记可以加我私聊欧。

面试官🤓:嗯,还可以,那你来给我聊一聊关于ThreadPoolExecutor创建线程池的方式吧。以及它构造方法中的几个参数吧。

派大星🙋🏻‍♂️:好的。。

好了上面的第一种创建方式的线程池已经介绍完了,但是对于面试官来说肯定是远远不够的。接下来我给大家说一下第二种方式:

关于ThreadPoolExecutor创建线程池的方式
通过 ThreadPoolExecutor的方式创建线程是阿里巴巴规范强烈推荐的。接下来我们聊一下吧。

关于其构造方法中的的几个参数分别是
参数说明:

  • corePoolSize:核心线程数目(最多保留的线程数)
  • maximumPoolSize:最大线程数目 (核心线程数+救急线程数)
  • keepAliveTime:生存时间 – 针对救急线程
  • unit:时间单位,针对的是救急线程
  • workQueue:阻塞队列
  • threaFactory:线程工厂 – 可以为线程创建时起个好名字
  • handler:拒绝策略 达到最大线程数后还有新的任务便会执行拒绝策略

  • jdk有核心线程和救急线程

救急线程数 = 最大线程数-核心线程数,一旦核心线程数用完,并且阻塞队列也满了,此时又来了任务,jdk会先判断有没有救急线程,如果有救急线程,它会执行任务。执行完就会释放该救急线程,它不会去执行阻塞队列里面的任务

  • 核心线程和救急线程的区别:
    一旦核心线程被创建它是不会被回收的一直保留,但是救急线程只有在阻塞队列满了并且核心线程也没有空闲才会被创建,否则就会被回收,可以简单理解为核心线程没有生存时间,救急线程有生存时间
  • 救急线程的前提:
    是配合有界队列,当任务超过了队列的大小时,会创建 maximumPoolSize-corePoolSize数目的线程来救急
  • 拒绝策略:
    1.AbortPolicy:让调用者抛出RejectedExecutionException异常,默认策略。
    2.CallerRunsPolicy:让调用者运行任务
    3.DiscardPolicy:放弃本次任务
    4.DiscardOldesPolicy:放弃队列中最早的任务,本任务取而代之。

派大星🙋🏻‍♂️:以上就是关于ThreadPoolExecutor的简单介绍。

好了为了更好的理解线程池我们可以手动创建一个线程池来模拟一下。


@Slf4j(topic = "c.TestPool")
public class TestPool {
    public static void main(String[] args) {
        ThreadPool threadPool = new ThreadPool(1,1000,TimeUnit.MILLISECONDS,1,(queue,task)->{
            // 死等
//            queue.put(task);
            // 2、带超时时间的等待
//            queue.offer(task,500,TimeUnit.MILLISECONDS);
            // 3、放弃任务执行
//            log.debug("放弃 {}",task);
            // 4、抛出异常
//            throw new RuntimeException("执行失败.."+task);
            // 5、让调用者自己执行任务....

            task.run();
        });
        for (int i = 0; i < 4; i++) {
            int j = i;
            threadPool.execute(()->{
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("{}",j);
            });
        }
    }
}

/**
 * 拒绝策略
 * @param
 */
@FunctionalInterface
interface RejectPolicy{
    void reject(BlockingQueue queue,T task);
}

@Slf4j(topic = "c.ThreadPool")
class ThreadPool{
    /**
     * 线程队列
     */
    private BlockingQueue taskQueue;
    /**
     * 线程集合
     */
    private HashSet workers = new HashSet<>();
    /**
     * 核心线程数
     */
    private int coreSize;

    /**
     * 获取任务的超时时间
     */

    private long timeout;

    /**
     * 拒绝策略
     */
    private RejectPolicy rejectPolicy;

    /**
     * 执行任务
     */
    public void execute(Runnable task){
        // 当任务数没有超过核心线程数,直接交给worker对象 执行
        // 如果任务数超过 coreSize时,加入任务队列暂存
        synchronized (workers){
            if (workers.size() rejectPolicy) {
        this.coreSize = coreSize;
        this.timeout = timeout;
        this.timeUnit = timeUnit;
        this.taskQueue = new BlockingQueue<>(queueCapacity);
        this.rejectPolicy = rejectPolicy;
    }

    /**
     * 时间单位
     */
    private TimeUnit timeUnit;

    class Worker extends Thread{
        private Runnable task;

        public Worker(Runnable task) {
            this.task = task;
        }

        @Override
        public void run() {
            // 执行任务
            // 1) 当task不为空,执行任务
            // 2) 当task执行完毕,再接着从任务队列获取任务执行
//            while (task!=null || (task = taskQueue.take() )!=null){
            while (task!=null || (task = taskQueue.poll(timeout,timeUnit) )!=null){ // 带有超时时间
                try {
                    log.debug(" 正在执行...{}",task);
                    task.run();
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    task = null;
                }
            }
            synchronized (workers){
                log.debug("worker 被移除 {}",this);
                workers.remove(this);
            }
        }
    }
}

@Slf4j(topic = "c.BlockingQueue")
class BlockingQueue{
    /**
     * 1.任务队列
     */
    private Deque queue = new ArrayDeque<>();
    /**
     * 2.锁
     */
    private ReentrantLock lock = new ReentrantLock();

    /**
     * 3.生产者条件变量
     */
    private Condition fullWaitSet = lock.newCondition();
    /**
     * 4.消费者条件变量
     */
    private Condition emptyWaitSet = lock.newCondition();
    /**
     * 5.容量
     */
    private int capacity;

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    /**
     * 带超时的等待获取
     */
    public T poll(long timeout, TimeUnit unit){
        lock.lock();
        try{
            // 将 timeout 统一转换为 纳秒
            long nanos = unit.toNanos(timeout);
            while (queue.isEmpty()){
                try {
                    // 返回的是剩余的时间
                    if (nanos rejectPolicy, T task) {
        lock.lock();
        try {
            // 判断队列是否已满
            if (queue.size() == capacity){
                rejectPolicy.reject(this,task);
            }else {// 有空闲
                log.debug("加入任务队列 {}",task);
                queue.addLast(task);
                emptyWaitSet.signal();
            }
        }finally {
            lock.unlock();
        }
    }
}

这是本人整理的线程池的相关思维导图:有需要的可以私聊我 JUC&#x5E76;&#x53D1;

关于线程池,面试的时候你时候还打怵,这里我有话要说保证让你对线程池的各个参数一边就懂

整理不易,如果对你有所帮助欢迎点赞关注
微信搜索【码上遇见你】获取更多精彩内容

Original: https://www.cnblogs.com/java-wang/p/16097242.html
Author: 码上遇见你
Title: 关于线程池,面试的时候你时候还打怵,这里我有话要说保证让你对线程池的各个参数一边就懂

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/580106/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Spring事务调用类自己方法失效解决办法和原因

    正常情况下,我们都是在controller里调用service里的方法,这个方法如果需要加事务,就在方法上加上@Transactional,这样是没问题的,事务会生效。 可是如果像…

    Java 2023年5月30日
    082
  • CentOS 7上重新编译安装nginx

    CentOS 7的源所提供的nginx几乎不包含任何扩展模块;为了能够使用一些扩展模块,我们需要从源代码重新编译安装nginx。 目前最新版的源代码是1.6.1。下载解压后先不要急…

    Java 2023年5月30日
    091
  • Java面向对象(四)

    Java面向对象(四) Java面向对象(四) – 十一、package 关键字(拓展) 11.1 package 关键字的使用: 11.2 包的作用: 11.3 Ja…

    Java 2023年6月9日
    080
  • 线程知识总结2

    参考文档:https://blog.csdn.net/weixin_45860338/article/details/113824249 B站:遇见狂神说 案例:线程停止 /** …

    Java 2023年6月7日
    093
  • 戏说领域驱动设计(十七)——实体实战

    上一节中讲了实体的一些概念,作为DDD中最为复杂的组件,想用好了还需要在实践中慢慢去摸索,都是摸爬滚打过来的。本章着重演示一些实体相关的代码,通过建立一个基类和通用方法,能让您在开…

    Java 2023年6月7日
    079
  • 【年度钻石】Linux云计算+运维(1)《博学谷》黑马

    Java互联网企业架构技术VIP课程【腾讯课堂每特】 Java互联网企业架构技术VIP课程【腾讯课堂每特】 课程 内容 站在架构角度,基于装饰模式纯手写设计多级缓存框架 本节课需要…

    Java 2023年6月7日
    080
  • 线索二叉树相关问题

    线索二叉树相关问题 1.在先序线索二叉树中求解指针P的后继结点 binode presuc(Binode *p){ if(p->rtag==1) return p->r…

    Java 2023年6月8日
    086
  • SpringMvc里的RequestBodyAdviceAdapter使用问题

    看了下源码,好像默认它转换的时候就截断了。 看了下源码,RequestBodyAdviceAdapter 这个静态类除了 beforeBodyRead 这个方法外,还有个 afte…

    Java 2023年5月30日
    074
  • 线程池线程复用的原理

    前言 线程池最大的作用就是复用线程。在线程池中,经过同一个线程去执行不一样的任务,减少反复地创建线程带来的系统开销,就是线程的复用。那么线程池线程复用的原理是什么呢? 之前面试被问…

    Java 2023年6月5日
    085
  • 数组

    一.数组的定义格式 数组是存储同一种数据类型多个元素的容器。 数组既可以存储基本数据类型,也可以存储引用数据类型。 格式1:数据类型[] 数组名;(常用) 格式2:数据类型 数组名…

    Java 2023年6月5日
    095
  • Java高并发教程:Reactor反应器模式

    Java高并发教程:Reactor反应器模式 Reactor反应器模式 到目前为止, 高性能网络编程都绕不开反应器模式。很多著名的服务器软件或者中间件都是基于反应器模式实现的,如N…

    Java 2023年5月29日
    067
  • 笔记:Java集合框架(一)

    Java集合框架(一) Collection接口 继承结构 Iterator接口 Iterator接口定义了迭代器的基本方法: java;gutter:true; hasNext(…

    Java 2023年6月7日
    076
  • cocosCreator定制小游戏构建模板

    1. 解决痛点 在开发微信小游戏过程中,需要在微信小游戏game.json加入一个配置键 navigateToMiniProgramAppIdList,但常规通过构建发布game….

    Java 2023年6月13日
    095
  • 注解@PostConstruct分析

    1.注解@PostConstruct可以添加在类的方法上面,如果这个类被IOC容器托管,那么在对Bean进行初始化前的时候会调用被这个注解修饰的方法 被定义在哪里? 1.被定义在了…

    Java 2023年6月15日
    077
  • Mybatis运行原理

    Mybatis运行原理 先是简要的文字说明,后面放上图。如有叙述错误,感谢指正哈。 一、 根据配置文件创建SQLSessionFactory 首先将配置文件转化为流,创建SqlSe…

    Java 2023年6月8日
    091
  • Python 的线程与进程

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Java 2023年6月8日
    092
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球