线程池总结

文章介绍了线程池的概念,强调其能提高线程创建效率并减少系统开销。标准库中的`Executors`类提供了多种线程池创建方式,如`newFixedThreadPool`、`newCachedThreadPool`等。文章还详细解析了`ThreadPoolExecutor`的构造参数,并概述了线程池的执行流程及四种拒绝策略。最后,通过一个简单的线程池模拟示例解释了线程池的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为什么要有线程池

线程的创建虽然比进程轻量, 但是在频繁创建情况下, 系统的开销是不可忽略的.
创建个线程池, 我们就可以从线程池拿线程, 这是纯粹的用户态操作.
如果从系统创建线程, 则涉及到用户态与内核态间的切换, 真正的创建是在内核态完成的.
而纯用户态操作时间是可控的, 涉及到内核态时间就不可控.

结论 : 线程池可以提高线程创建效率, 减少每次启动, 销毁线程的损耗.

标准库中的线程池

标准库中提供了现成的线程池, 可以通过下面代码来创键:

	ExecutorService poll = Executors.newFixedThreadPool(10);

使用 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池.
返回值类型为 ExecutorService.
通过 ExecutorService.submit 可以添加一个任务到线程池中.

为什么这里不是直接 new 对象, 而是通过调用 Executors 类的静态方法来创建对象.

这里就涉及到了工厂模式, 啥是工厂模式呢?

创建对象并非直接 new, 而是使用一些其他的方法 (通常是静态方法) 协助我们把对象创建出来.

工厂模式是用来填构造方法的坑的.
举个例子 :
在一个类里, 要想提供多种不同的构造对象的方式就得基于重载.
但重载规定参数列表必须不同, 这就出现了一个问题, 如果我们想通过相同的参数构造不同的对象呢?

比如这个类 :
在这里插入图片描述
为了解决上述问题, 我们可以构造一个工厂类 :
在这里插入图片描述
这便是工厂模式.

Executors 创建线程池的几种方式

  1. newFixedThreadPool: 创建固定线程数的线程池
public class Test {
    public static void main(String[] args) {
        ExecutorService poll = Executors.newFixedThreadPool(10);
        poll.submit(new Runnable() { //将任务添加进线程池中
            @Override
            public void run() {
                System.out.println("111");
            }
        });
        System.out.println("222");
    }
}

在这里插入图片描述
可以看到线程池执行完任务后并没有结束, 而是一直运行, 其实它里面内置了线程来执行任务, 是前台线程, 会阻止线程的结束.

  1. newCachedThreadPool: 创建线程数目动态增长的线程池.
public class Test {
    public static void main(String[] args) {
        ExecutorService poll = Executors.newCachedThreadPool();
        poll.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("111");
            }
        });
        System.out.println(222);
    }
}
  1. newSingleThreadExecutor: 创建只包含单个线程的线程池.
public class Test {
    public static void main(String[] args) {
        ExecutorService poll = Executors.newSingleThreadExecutor();
        poll.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("111");
            }
        });
        System.out.println(222);
    }
}
  1. newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer.
public class Test {
    public static void main(String[] args) {
        ScheduledExecutorService poll = Executors.newScheduledThreadPool(1);
        poll.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("111");
            }
        },3, TimeUnit.SECONDS); //设置3S后执行
        System.out.println(222);
    }
}  

在这里插入图片描述

线程池的简单模拟

class MyThreadPool {
    private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }
    //实现一个固定线程的线程池
    public MyThreadPool(int n) throws InterruptedException {
        for(int i = 0; i < n; i++) {  //循环n次, 创建n个线程
            Thread t = new Thread(() -> {
                while(true) {   //保证每个线程一直在取任务
                    try {
                        Runnable runnable = queue.take();
                        runnable.run();   //执行任务
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();  //启动线程
        }
    }
}
public class ThreadDemo3 {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool myThreadPool = new MyThreadPool(10);
        for(int i = 0; i < 10000; i++) {
            int m = i;
            myThreadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("打印数字: " + m);  //注意这里不能为i
                }
            });
        }
    }
}

在这里插入图片描述
为什么打印数字要用 m 而不直接用 i 呢? 定义 m 变量是多此一举吗?
这里和 lambda 表达式有关, 这里虽然没有用到 lambda 表达式, 但是这里用到了匿名内部类, lambda 表达式本质上也是匿名内部类.

lambda 表达式中捕获的变量必须是 final 修饰的 或者是 “实际 final”(没有被final修饰, 但是代码中没有对该变量进行修改过)

这里 i 就是一直被修改的变量, 而 m 不一样, 我们每次进入循环都会再创建一个 m , 这样保证每次循环里的 m 都不一样, 每个 m 都不会改变, 这就满足了“实际 final”.

Executors 本质上是 ThreadPoolExecutor 类的封装, 也就是说 Executors 是一个工厂类.

ThreadPoolExecutor 的参数介绍

我们可以在 java.util.concurrent 下找到该类.
在这里插入图片描述

来看看它的构造方法:
在这里插入图片描述
要明白构造方法, 首先要明白它的参数意义, 我们就拿它参数最多的构造方法来说明 :
在这里插入图片描述

  1. corePoolSize : 核心线程数(最主要的线程, 不会被销毁)
  2. maximumPoolSize : 最大线程数(核心线程数 + 临时线程数)

如果当前任务比较多, 线程池就会多创建一些 “临时线程”, 当任务少了, 比较空闲了, 线程池就会把多出来的临时线程销毁掉.(核心线程不会动)

  1. keepAliveTime : 保持存活的最大时间.(当任务比较少时, 整体空闲下来的时候, 临时线程不会立刻被销毁, 而是会存活一段时间, 等待任务, 如果这段时间内还没有接到新任务, 那就会被销毁)

  2. BlockingQueue workQueue : 线程池要管理很多任务, 这些任务是通过阻塞队列来组织的, 我们可以手动指定的给线程池一个队列, 此时就可以很方便的控制 / 获取队列的信息了, submit方法就是将任务放到该队列中.

  3. ThreadFactory threadFactory : 工厂类, 就是创建线程的辅助类.

  4. RejectedExecutionHandler handler : 线程池的拒接策略.(如果线程池的池子满了, 继续往里添加任务, 如何拒绝)

线程池的执行流程

在这里插入图片描述
当添加新任务时, 首先进行判断, 判断核心线数是否为满, 如果没满, 就创建核心线程执行任务, 如果核心线程已满, 则判断任务队列是否有地方存放该任务, 如果有, 就将任务保存在任务队列中, 等待执行, 如果满了, 再判断最大可容纳的线程数, 如果没有超过这个数量, 就创建非核心线程执行任务, 如果超出, 就调用 handler 实现拒绝策略.

标准库中提供的四种拒绝策略(经典面试题)

在这里插入图片描述
ThreadPoolExecutor.AbortPolicy : 如果线程池满了, 继续添加任务,则会直接抛出异常.
ThreadPoolExecutor.CallerRunsPolicy : 添加的线程自己负责执行该任务.(哪个线程将该任务给它, 哪个线程就去负责执行)
ThreadPoolExecutor.DiscardOldestPolicy : 丢弃最老的任务.(运行时间最长的任务)
ThreadPoolExecutor.DiscardPolicy : 丢弃最新任务.(也就是要添加的任务)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

随风的浪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值