线程池(面试常考)

简介: 关于“池”的概念,我们接触过字符串常量池,数据库连接池,它们都被用作共享和缓存资源,通俗的将就是使用的时候直接从池子里拿,线程池也一样,在初始化的时候,就创建一定数量元素,后面需要使用线程就直接从线程池中取。

🍊一. 认识线程池

关于“池”的概念,我们接触过字符串常量池,数据库连接池,它们都被用作共享和缓存资源,通俗的将就是使用的时候直接从池子里拿,线程池也一样,在初始化的时候,就创建一定数量元素,后面需要使用线程就直接从线程池中取。


我们之前使用多线程的时候都会创建线程和销毁线程,但是创建和销毁都会耗费大量资源,所以线程池的作用就是减小创建和销毁时的损耗


🍬举例形象说明线程池:


我们假设线程池为一个快递公司,里面有正式员工,但是在双11,双12因为快递量大所以有临时工,但是高峰期过后需要解雇临时工,快递都放在仓库,当仓库满了之后就不在接收快递,快递员送快递是从仓库拿快递然后送


🍉二. 原生线程池(ThreadPoolExecutor)

ThreadPoolExecutor提供了更多的参数,可以进一步细化线程池的行为


🍬ThreadPoolExecutor的构造方法:

微信图片_20221029144449.jpg

🍬构造方法参数解析:对应上述例子来结合理解


🍃corePoolSize,核心线程数:正式员工

🍃maximumPoolSize,最大线程数:正式员工和临时员工总数

🍃keepAliveTime,空闲时间:临时工空闲(keepAliveTime,TimeUnit结合来决定何时解雇临时工)

🍃TimeUnit,空闲时间单位:空闲时间(keepAliveTime,TimeUnit结合来决定何时解雇临时工)

🍃workQueue,阻塞队列:快递仓库

🍃threadFactory:使用工厂对象提供的方法来创建线程(了解)

🍃RejectedExecutionHandler,拒绝策略:仓库满了不再接收快递


🍬拒绝策略:


🍂AbortPolicy():以抛出异常的方式拒绝(默认的拒绝策略)

🍂CallerRunsPolicy():让调用的线程来处理

🍂DiscardOldestPolicy():丢弃时间最久的任务(先进先出)

🍂DiscardPolicy():丢弃新来的任务


👁‍🗨️实现代码:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
//使用原生api来创建(ThreadPoolExecutor)
public class ThreadPool {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3, //线程核心数
                10, //最大线程数
                60,  //空闲时间
                TimeUnit.SECONDS, //空闲时间单位
                new ArrayBlockingQueue<>(100), //阻塞队列
                new ThreadPoolExecutor.AbortPolicy()  //默认拒绝策略,抛出异常
//                new ThreadPoolExecutor.CallerRunsPolicy() 让调用线程自己处理
//                new ThreadPoolExecutor.DiscardOldestPolicy() 丢弃时间最久任务
//                new ThreadPoolExecutor.DiscardPolicy() 丢弃新来的任务
        );
        //提交任务使用:submit/execute
        for(int i = 0;i < 10;i++){
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }
    }
}


🍏三. ExecutorService 和 Executors创建线程池

这种创建方式对应就是ThreadPoolExecutor构造参数中的threadFactory参数,使用工厂对象提供的创建方式


ExecutorService 表示一个线程池实例

Executors 是一个工厂类, 能够创建出几种不同风格的线程池


提交任务到线程池中的阻塞队列中:

🍁execute(Runnable task)

🍁submit(Runnable task)

🍁submit(Callable task)


🍬Executors创建线程池的几种方式:i


☘️newFixedThreadPool:创建固定线程数的线程池

☘️newCachedThreadPool:创建线程数目动态增长的线程池

☘️newSingleThreadExecutor:创建只包含单个线程的线程池

☘️newScheduledThreadPool:创建有计划任务的线程池(带有定时器功能)


👁‍🗨️示例代码:

//创建有缓存的线程池
        ExecutorService pool1 = Executors.newCachedThreadPool();
        //创建有固定大小的线程池
        ExecutorService pool2 = Executors.newFixedThreadPool(4);
        //创建有计划任务的线程池
        ExecutorService pool3 = Executors.newScheduledThreadPool(4);
        //创建只有单个线程的线程池
        ExecutorService pool4 = Executors.newSingleThreadExecutor();

   

👁‍🗨️注意:这种创建的方式在以后的工作中不建议用


🍂在工作中要阻塞队列设置大小,如果不设置大小,在某个时间会导致内存不够,出现OOM(内存溢出)

🍂拒绝策略要自己扩展实现(比如任务记录在日志或者数据库里)


🫐四. 线程池的工作流程

结合上述快递公司例子说明:

image.png



🍋五. 线程池的模拟实现

前提说明:


🍁这里阻塞队列的实现采用链表的方式阻塞

🍁我们要求线程池创建的时候,就创建线程不停的从队列中取任务来执行

🍁这里创建5个员工


👁‍🗨️代码实现:

import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
public class MyThreadPool {
    private BlockingDeque<Runnable> queue = new LinkedBlockingDeque<>();
    //员工的数量
    public MyThreadPool(int num){
        for(int i = 0;i < num;i++){
            //线程池创建的时候,就创建线程不停的从队列中取任务来执行
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){ //不停的取任务
                        try {
                            Runnable task = queue.take(); //获取任务
                            task.run(); //执行任务
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    }
    //放任务
    public void execute(Runnable task){
        try {
            queue.put(task);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool pool = new MyThreadPool(5);
        while(true){
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            });
            Thread.sleep(1000);
        }
    }
}


部分打印结果:

微信图片_20221029144628.jpg

目录
打赏
0
0
0
0
4
分享
相关文章
阿里面试:5000qps访问一个500ms的接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?
本文由40岁老架构师尼恩撰写,针对一线互联网企业的高频面试题“如何确定系统的最佳线程数”进行系统化梳理。文章详细介绍了线程池设计的三个核心步骤:理论预估、压测验证和监控调整,并结合实际案例(5000qps、500ms响应时间、4核8G机器)给出具体参数设置建议。此外,还提供了《尼恩Java面试宝典PDF》等资源,帮助读者提升技术能力,顺利通过大厂面试。关注【技术自由圈】公众号,回复“领电子书”获取更多学习资料。
面试大神教你:如何巧妙回答线程优先级这个经典考题?
大家好,我是小米。本文通过故事讲解Java面试中常见的线程优先级问题。小明和小华的故事帮助理解线程优先级:高优先级线程更可能被调度执行,但并非越高越好。实际开发需权衡业务需求,合理设置优先级。掌握线程优先级不仅能写出高效代码,还能在面试中脱颖而出。最后,小张因深入分析成功拿下Offer。希望这篇文章能助你在面试中游刃有余!
86 4
面试大神教你:如何巧妙回答线程优先级这个经典考题?
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
288 14
面试中的难题:线程异步执行后如何共享数据?
本文通过一个面试故事,详细讲解了Java中线程内部开启异步操作后如何安全地共享数据。介绍了异步操作的基本概念及常见实现方式(如CompletableFuture、ExecutorService),并重点探讨了volatile关键字、CountDownLatch和CompletableFuture等工具在线程间数据共享中的应用,帮助读者理解线程安全和内存可见性问题。通过这些方法,可以有效解决多线程环境下的数据共享挑战,提升编程效率和代码健壮性。
185 6
Java线程调度揭秘:从算法到策略,让你面试稳赢!
在社招面试中,关于线程调度和同步的相关问题常常让人感到棘手。今天,我们将深入解析Java中的线程调度算法、调度策略,探讨线程调度器、时间分片的工作原理,并带你了解常见的线程同步方法。让我们一起破解这些面试难题,提升你的Java并发编程技能!
199 16
面试直击:并发编程三要素+线程安全全攻略!
并发编程三要素为原子性、可见性和有序性,确保多线程操作的一致性和安全性。Java 中通过 `synchronized`、`Lock`、`volatile`、原子类和线程安全集合等机制保障线程安全。掌握这些概念和工具,能有效解决并发问题,编写高效稳定的多线程程序。
186 11
硬核揭秘:线程与进程的底层原理,面试高分必备!
嘿,大家好!我是小米,29岁的技术爱好者。今天来聊聊线程和进程的区别。进程是操作系统中运行的程序实例,有独立内存空间;线程是进程内的最小执行单元,共享内存。创建进程开销大但更安全,线程轻量高效但易引发数据竞争。面试时可强调:进程是资源分配单位,线程是CPU调度单位。根据不同场景选择合适的并发模型,如高并发用线程池。希望这篇文章能帮你更好地理解并回答面试中的相关问题,祝你早日拿下心仪的offer!
123 6
Java 多线程 面试题
Java 多线程 相关基础面试题
【JavaEE】——单例模式引起的多线程安全问题:“饿汉/懒汉”模式,及解决思路和方法(面试高频)
单例模式下,“饿汉模式”,“懒汉模式”,单例模式下引起的线程安全问题,解锁思路和解决方法

热门文章

最新文章

  • 1
    云计算运维工程师面试技巧
    900
  • 2
    【机器学习】面试问答:PCA算法介绍?PCA算法过程?PCA为什么要中心化处理?PCA为什么要做正交变化?PCA与线性判别分析LDA降维的区别?
    317
  • 3
    【机器学习】面试问答:决策树如何进行剪枝?剪枝的方法有哪些?
    233
  • 4
    【机器学习】SVM面试题:简单介绍一下SVM?支持向量机SVM、逻辑回归LR、决策树DT的直观对比和理论对比,该如何选择?SVM为什么采用间隔最大化?为什么要将求解SVM的原始问题转换为其对偶问题?
    210
  • 5
    【深度学习】Pytorch面试题:什么是 PyTorch?PyTorch 的基本要素是什么?Conv1d、Conv2d 和 Conv3d 有什么区别?
    732
  • 6
    【深度学习】TensorFlow面试题:什么是TensorFlow?你对张量了解多少?TensorFlow有什么优势?TensorFlow比PyTorch有什么不同?该如何选择?
    562
  • 7
    【机器学习】面试题:LSTM长短期记忆网络的理解?LSTM是怎么解决梯度消失的问题的?还有哪些其它的解决梯度消失或梯度爆炸的方法?
    503
  • 8
    【数据挖掘】XGBoost面试题:与GBDT的区别?为什么使用泰勒二阶展开?为什么可以并行训练?为什么快?防止过拟合的方法?如何处理缺失值?
    558
  • 9
    【数据挖掘】 GBDT面试题:其中基分类器CART回归树,节点的分裂标准是什么?与RF的区别?与XGB的区别?
    188
  • 10
    【机器学习】过拟合和欠拟合怎么判断,如何解决?(面试回答)
    1006
  • AI助理

    你好,我是AI助理

    可以解答问题、推荐解决方案等

    登录插画

    登录以查看您的控制台资源

    管理云资源
    状态一览
    快捷访问