线程池(面试常考)

🍊一. 认识线程池

关于"池"的概念,我们接触过字符串常量池,数据库连接池,它们都被用作共享和缓存资源,通俗的将就是使用的时候直接从池子里拿,线程池也一样,在初始化的时候,就创建一定数量元素,后面需要使用线程就直接从线程池中取
我们之前使用多线程的时候都会创建线程和销毁线程,但是创建和销毁都会耗费大量资源,所以线程池的作用就是减小创建和销毁时的损耗

🍬举例形象说明线程池:

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

🍉二. 原生线程池(ThreadPoolExecutor)

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

🍬ThreadPoolExecutor的构造方法:

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

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

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

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

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

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

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

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

🍬拒绝策略:

🍂AbortPolicy(): 以抛出异常的方式拒绝(默认的拒绝策略)
🍂CallerRunsPolicy(): 让调用的线程来处理
🍂DiscardOldestPolicy(): 丢弃时间最久的任务(先进先出)
🍂DiscardPolicy(): 丢弃新来的任务

👁‍🗨️实现代码:

java 复制代码
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:创建有计划任务的线程池(带有定时器功能)

👁‍🗨️示例代码:

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

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

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

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

🫐四. 线程池的工作流程

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

🍋五. 线程池的模拟实现

前提说明:

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

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

🍁这里创建5个员工

👁‍🗨️代码实现:

java 复制代码
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);
        }
    }
}

部分打印结果:

相关推荐
m0_571957581 小时前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
魔道不误砍柴功3 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2343 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨3 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
Chrikk5 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*5 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue5 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man5 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
测开小菜鸟5 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity6 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq