Java-线程池面试题

Java-线程池面试题线程池前言什么是线程池为什么要使用线程池线程池有哪些作用线程池的创建方式如何实现复用ThreadPoolExecutor核心参数其他相关总结前言线程池在面试、开发过程中都比较重要。本文总结了一些关于该方面的相关知识点。以下内容收集于蚂蚁课堂什么是线程池线程池和数据库连接池非常类似,可以统一管理和维护线程,减少没有必要的开销。为什么要使用线程池因为在项目开发过程中频繁的开启线程或者停止线程,线程需要重新被CPU从就绪状态调度到运行状态,需要发生CPU的上下文切换,效率非常低。线程的生命周期如

大家好,又见面了,我是你们的朋友全栈君。

前言

线程池在面试、开发过程中都比较重要。本文总结了一些关于该方面的相关知识点。
以下内容收集于 蚂蚁课堂

什么是线程池

线程池和数据库连接池非常类似,可以统一管理和维护线程,减少没有必要的开销。

为什么要使用线程池

因为在项目开发过程中频繁的开启线程或者停止线程,线程需要重新被CPU从就绪状态调度到运行状态,需要发生CPU的上下文切换,效率非常低。

线程的生命周期如下图所示:
在这里插入图片描述

线程池有哪些作用

  • 降低资源消耗:通过池化技术重复利用已创建好的线程,降低线程创建和销毁造成的损耗。
  • 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
  • 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
  • 提供更多强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延迟定时线程池 ScheduledThreadPoolExecutor ,就允许任务延期执行或定期执行。

线程池的创建方式

分为以下几种创建方式:

  • Executors.newCachedThreadPool():可缓存线程池
  • Executors.newFixedThreadPool():可定长度,限制最大线程数
  • Executors.newScheduledThreadPool():可定时线程池
  • Executors.newSingleThreadExecutor():单例线程池
    底层都是基于 ThreadPoolExecutor 构造函数封装

如何实现复用

本质思想:创建一个线程,不会立马体质或者销毁,而是一直实现复用。

  • 提前创建固定大小的线程一直保持正在运行的状态(可能会非常消耗CPU资源)。
  • 当需要线程执行任务,将该任务提交缓存在并发队列中,如果缓存队列满了,则会执行拒绝策略。
  • 正在运行的线程从并发队列中获取任务执行从而实现线程复用的问题。
    线程池底层原理如下图所示:
    在这里插入图片描述
    线程池核心点:复用机制
    • 提前创建好固定的线程一直在运行状态—死循环实现。
    • 提交的线程任务缓存到一个并发队列集合中,交给我们正在运行的线程执行。
    • 正在运行的线程就从队列中获取该任务执行。

简单实现代码如下:

/** * @author zfl_a * @date 2021/3/20 * @project multi-thread */
public class CustExcutors { 
   

    // 存放线程任务
    public BlockingDeque<Runnable> runnableList ;

    // 停止线程标识位
    private volatile Boolean isRun = true ;

    /** * 初始化 * @param dequeSize 队列容器大小 * @param threadCount 线程池大小 */
    public CustExcutors(int dequeSize,int threadCount){ 
   
        runnableList = new LinkedBlockingDeque<>(dequeSize);
        for (int i=0;i<threadCount;i++) { 
   
            WorkThread workThread = new WorkThread();
            workThread.start();
        }
    }

    public void execute(Runnable runnable){ 
   
        runnableList.offer(runnable);
    }

    class WorkThread extends Thread { 
   

        @Override
        public void run (){ 
   
            // 标识位位true 或者队列中有未执行完成的任务
            while(isRun || runnableList.size()>0) { 
   
                Runnable runnable = runnableList.poll();
                // 如果不为空 ,执行
                if(runnable!=null) { 
   
                    runnable.run();
                }
            }
        }
    }

    public static void main(String[] args) { 
   
        CustExcutors custExcutors = new CustExcutors(10,2);
        for (int x=0;x<10;x++) { 
   
            final int i = x ;
            custExcutors.execute(new Runnable() { 
   
                @Override
                public void run() { 
   
                    System.out.println(Thread.currentThread().getName()+"----"+i);
                }
            });
        }
        // 停止线程
        custExcutors.isRun=false;
    }
}

运行结果:只有两个线程在运行任务
在这里插入图片描述

ThreadPoolExecutor核心参数

  • corePoolSize:核心线程数量,一直正在保持运行的线程。
  • maximumPoolSize:最大线程数,线程池允许创建的最大线程数。
  • keepAliveTime:超出 corePoolSize 后创建的线程存活时间(当超过核心线程数后,又没有线程任务执行,达到该存活时间后,停止该线程)。
  • unit:keepAliveTime 的时间单位。
  • workQueue:任务队列,用于保持待执行的任务。
  • threadFactory :线程池内部创建线程所用的工厂。
  • handler:任务无法执行时的处理器(当任务被拒绝时)。

其他相关总结

  1. 线程池不会一直在运行状态。假设配置核心线程数 corePoolSize 为2 ,最大线程数 maximumPoolSize 为5,我们可以通过 corePoolSize 核心线程数后创建的线程的存活时间例如为60s,在60s内没有线程任务执行,则会停止该线程。

  2. 线程池底层 ThreadPoolExecutor 底层实现原理
    2.1. 当线程数小于核心线程数时,创建线程。
    2.2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
    2.3. 当线程数大于等于核心线程数,且任务队列已满,有以下两种情况。
        2.3.1. 如果线程数小于最大线程数,创建线程。
        2.3.2. 如果线程数等于最大线程数,抛出异常,拒绝任务。

  3. 如果队列满了,且任务总数>最大线程数则当前线程走拒绝策略。可自定义拒绝异常,将该任务缓存到Redis、本地文件、mysql中,后期项目启动实现补偿。

  4. 拒绝策略有以下几种:
    4.1. AbortPolicy:丢弃任务,抛出运行时异常。
    4.2. CallerRunsPolicy:执行任务。
    4.3. DiscardPolicy 忽视
    4.4. DiscardOldestPolicy:从队列中剔除最先进入队列(最后一个执行)的任务。
    4.5. 实现 RejectedExecutionHandler 接口,可自定义处理器。

  5. 如何合理配置参数
    自定义线程池就需要我们自己配置最大线程数 maximumPoolSize ,为了高效的并发运行,这时需要看我们的业务是IO密集型还是CPU密集型。

    5.1 CPU密集型:
    CPU密集的意思是该任务需要最大的运算,而没有阻塞,CPU一直全速运行。CPU密集任务只有在真正的多核CPU上才能得到加速(通过多线程)。而在单核CPU上,无论你开几个模拟的多线程该任务都不可能得到加速,因为CPU总的运算能力就那么多。

    5.2 IO密集型
    IO密集型,即该任务需要大量的IO,即大量的阻塞。在单线程上运行IO密集型的任务会导致大量的CPU运算能力浪费在等待。所以在IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上这种加速主要就是利用了被浪费掉的阻塞时间。

    IO 密集型时,大部分线程都阻塞,故需要多配制线程数。公式为:

    CPU核数*2
    CPU核数/(1-阻塞系数) 阻塞系数在0.8~0.9之间
    查看CPU核数:
    System.out.println(Runtime.getRuntime().availableProcessors());
    
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/141560.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 曲线积分_曲线积分的几何意义

    曲线积分_曲线积分的几何意义曲线积分曲面积分第一类曲线积分和第二类曲线积分第一类曲线积分$L$为$R^{3}$中的可求导的长曲线,函数$f(x,y,z)$在$L$上有定义习题:$\int\limits_{L}|x|^

    2022年8月3日
    5
  • excel截取中间字符串函数_java截取指定字符串

    excel截取中间字符串函数_java截取指定字符串应用场景办公过程中需要使用某一单元格内的特定部分,因此需要对字符串进行截取操作。例如单元格内容是这样的:AAA\aaa我们需要的内容是这样的:aaaExcel实现函数部分如下:=RIGHT(A1,LEN(A1)-FIND(“\”,A1,1))==========================================================个人能力有限,如有谬误欢迎指正!…

    2025年6月15日
    2
  • pkl文件是什么_python pkl文件

    pkl文件是什么_python pkl文件这里只介绍关于字典类型和列表类型的数据的存储,其他格式的请君自行探索。1、字典类型importpickle#首先导入这个库,没有安装的话,自行百度,很简单dict_data={“name”:[“张三”,”李四”]}withopen(“dict_data.pkl”,’wb’)asfo:#将数据写入pkl文件pickle.dump(dict_data,fo)with…

    2025年9月8日
    6
  • owasp靶机安装_靶机是什么

    owasp靶机安装_靶机是什么owasp靶机应该是每个渗透入门乃至一些高手的一个试炼地之一,以下是我在靶机上搭建虚拟的方法。1.虚拟机安装在这里我用的是VMware14,靶机自然也是VM的。软件安装基本也是没有什么可讲的,安

    2022年8月3日
    6
  • 虚拟机中安装GHO文件配置说明[通俗易懂]

    虚拟机中安装GHO文件配置说明[通俗易懂]虚拟机安装系统的配置说明(GHO文件)

    2022年7月4日
    392
  • 自建电驴服务器,电驴服务器怎么连接 eMule连接服务器教程

    自建电驴服务器,电驴服务器怎么连接 eMule连接服务器教程电驴(eMule)是一款非常实用的资源下载工具。但有时候用户会反映,电驴连接不到服务器的情况,今天小编就跟大家讲讲电驴服务器怎么连接,让你轻松下载到自己需要的资源。eMule连接服务器教程步骤一:在电驴服务器界面右边的“从URL更新server.met”字样下边的小框里输入“”,然后点击“更新”,下载新的服务器列表即可(使用的网址不要emule.org.cn提供的)步骤二:解决kad网络无法连接1…

    2022年6月18日
    171

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号