线程池(面试常考)

简介: 线程池(面试常考)

🍊一. 认识线程池

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

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

🍬举例形象说明线程池:

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

🍉二. 原生线程池(ThreadPoolExecutor)

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

🍬ThreadPoolExecutor的构造方法:

image.png

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

🍃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创建线程池的几种方式:

☘️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 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);
        }
    }
}

部分打印结果:

image.png



相关文章
|
2天前
|
Java 数据库连接 调度
面试题:用过线程池吗?如何自定义线程池?线程池的参数?
字节跳动面试题:用过线程池吗?如何自定义线程池?线程池的参数?
31 0
|
2天前
|
Java 程序员
java线程池讲解面试
java线程池讲解面试
67 1
|
18小时前
|
机器学习/深度学习 PyTorch 算法框架/工具
神经网络基本概念以及Pytorch实现,多线程编程面试题
神经网络基本概念以及Pytorch实现,多线程编程面试题
|
22小时前
|
Java
阅读《代码整洁之道》总结(1),java多线程面试
阅读《代码整洁之道》总结(1),java多线程面试
|
22小时前
|
缓存 安全 Java
7张图带你轻松理解Java 线程安全,java缓存机制面试
7张图带你轻松理解Java 线程安全,java缓存机制面试
|
23小时前
|
消息中间件 前端开发 Java
美团面试:如何实现线程任务编排?
线程任务编排指的是对多个线程任务按照一定的逻辑顺序或条件进行组织和安排,以实现协同工作、顺序执行或并行执行的一种机制。 ## 1.线程任务编排 VS 线程通讯 有同学可能会想:那线程的任务编排是不是问的就是线程间通讯啊? 线程间通讯我知道了,它的实现方式总共有以下几种方式: 1. Object 类下的 wait()、notify() 和 notifyAll() 方法; 2. Condition 类下的 await()、signal() 和 signalAll() 方法; 3. LockSupport 类下的 park() 和 unpark() 方法。 但是,**线程通讯和线程的任务编排是
|
2天前
|
Java
【Java多线程】面试常考 —— JUC(java.util.concurrent) 的常见类
【Java多线程】面试常考 —— JUC(java.util.concurrent) 的常见类
24 0
|
2天前
|
安全 Java 程序员
【Java多线程】面试常考——锁策略、synchronized的锁升级优化过程以及CAS(Compare and swap)
【Java多线程】面试常考——锁策略、synchronized的锁升级优化过程以及CAS(Compare and swap)
12 0
|
2天前
|
消息中间件 监控 前端开发
面试官:核心线程数为0时,线程池如何执行?
线程池是 Java 中用于提升程序执行效率的主要手段,也是并发编程中的核心实现技术,并且它也被广泛的应用在日常项目的开发之中。那问题来了,如果把线程池中的核心线程数设置为 0 时,线程池是如何执行的? 要回答这个问题,我们首先要了解在正常情况下,线程池的执行流程,也就是说当有一个任务来了之后,线程池是如何运行的? ## 1.线程池的执行流程 正常情况下(核心线程数不为 0 的情况下)线程池的执行流程如下: 1. **判断核心线程数**:先判断当前工作线程数是否大于核心线程数,如果结果为 false,则新建线程并执行任务。 2. **判断任务队列**:如果大于核心线程数,则判断任务队列是否
22 1
面试官:核心线程数为0时,线程池如何执行?
|
2天前
|
监控 安全 Java
【多线程学习】深入探究阻塞队列与生产者消费者模型和线程池常见面试题
【多线程学习】深入探究阻塞队列与生产者消费者模型和线程池常见面试题