【JavaEE】线程池

🌷 线程池是什么

虽然创建线程 / 销毁线程 的开销

想象这么一个场景:
在学校附近新开了一家快递店,老板很精明,想到一个与众不同的办法来经营。店里没有雇人, 而是每次有业务来了,就现场找一名同学过来把快递送了,然后解雇同学。这个类比我们平时来一个任务,起一个线程进行处理的模式。
很快老板发现问题来了,每次招聘 + 解雇同学的成本还是非常高的。老板还是很善于变通的,知道了为什么大家都要雇人了,所以指定了一个指标,公司业务人员会扩张到 3 个人,但还是随着业务逐步雇人。于是再有业务来了,老板就看,如果现在公司还没 3 个人,就雇一个人去送快递,否则只是把业务放到一个本本上,等着 3 个快递人员空闲的时候去处理。这个就是我们要带出的线程池的模式。

线程池最大的好处就是减少每次启动、销毁线程的损耗

🌷 标准库中的线程池

  • 使用 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池.
  • 返回值类型为 ExecutorService
  • 通过 ExecutorService.submit 可以注册一个任务到线程池中.
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {
    @Override
    public void run() {
        System.out.println("hello");
   }
});

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

  • newFixedThreadPool: 创建固定线程数的线程池
  • newCachedThreadPool: 创建线程数目动态增长的线程池.
  • newSingleThreadExecutor: 创建只包含单个线程的线程池.
  • newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer.

Executors 本质上是 ThreadPoolExecutor 类的封装
ThreadPoolExecutor 提供了更多的可选参数, 可以进一步细化线程池行为的设定.

六种创建线程池的方式:

public class Demo01_Threadpool {
    public static void main(String[] args) {
        // 1. 用来处理大量短时间工作任务的线程池,如果池中没有可用的线程将创建新的线程,如果线程空闲60秒将收回并移出缓存
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        // 2. 创建一个操作无界队列且固定大小线程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        // 3. 创建一个操作无界队列且只有一个工作线程的线程池
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        // 4. 创建一个单线程执行器,可以在给定时间后执行或定期执行。
        ScheduledExecutorService singleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();
        // 5. 创建一个指定大小的线程池,可以在给定时间后执行或定期执行。
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
        // 6. 创建一个指定大小(不传入参数,为当前机器CPU核心数)的线程池,并行地处理任务,不保证处理顺序
        Executors.newWorkStealingPool();
    }
}

🌷 简单使用线程池

public class Demo03_ThreadPoolUse {
    public static void main(String[] args) throws InterruptedException {
        // 创建一个线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        // 提交任务到线程池
        for (int i = 0; i < 10; i++) {
            int taskId = i;
            threadPool.submit(() -> {
                System.out.println("我是任务 " + taskId + ", " + Thread.currentThread().getName());
            });
        }

        // 模拟等待任务
        TimeUnit.SECONDS.sleep(5);
        System.out.println("第二阶段开始");

        // 提交任务到线程池
        for (int i = 10; i < 20; i++) {
            int taskId = i;
            threadPool.submit(() -> {
                System.out.println("我是任务 " + taskId + ", " + Thread.currentThread().getName());
            });
        }
    }
}

🌷 实现线程池

核心操作为 submit, 将任务加入线程池中

  • 使用 Worker 类描述一个工作线程. 使用 Runnable 描述一个任务.
  • 使用一个 BlockingQueue 组织所有的任务
  • 每个 worker 线程要做的事情: 不停的从 BlockingQueue 中取任务并执行.
  • 指定一下线程池中的最大线程数 maxWorkerCount; 当当前线程数超过这个最大值时, 就不再新增线程了.
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class MyThreadPool {
    // 1. 定义一个阻塞队列来保存我们的任务
    BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(3);

    // 2. 对外提供一个方法,用来往队列中提交任务
    public void submit (Runnable task) throws InterruptedException {
        queue.put(task);
    }

    // 3. 构造方法
    public MyThreadPool (int capacity) {
        if (capacity <= 0) {
            throw new RuntimeException("线程数量不能小于0.");
        }
        // 完成线程的创建,扫描队列,取出任务并执行
        for (int i = 0; i < capacity; i++) {
            // 创建线程
            Thread thread = new Thread(() -> {
                while (true) {
                    try {
                        // 取出任务(扫描队列的过程)
                        Runnable take = queue.take();
                        // 执行任务
                        take.run();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            // 启动线程
            thread.start();

        }
    }
}

import java.util.concurrent.TimeUnit;

public class Demo04_MyThreadPool {

    public static void main(String[] args) throws InterruptedException {
        // 创建自定义的线程池
        MyThreadPool threadPool = new MyThreadPool(3);
        // 往线程池中提交任务
        for (int i = 0; i < 10; i++) {
            int taskId = i;
            threadPool.submit(() -> {
                System.out.println("我是任务 " + taskId + ", " + Thread.currentThread().getName());
            });
        }

        // 模拟等待任务
        TimeUnit.SECONDS.sleep(3);
        System.out.println("第二阶段开始");

        // 提交任务到线程池
        for (int i = 10; i < 20; i++) {
            int taskId = i;
            threadPool.submit(() -> {
                System.out.println("我是任务 " + taskId + ", " + Thread.currentThread().getName());
            });
        }
    }
}

🌷 一些面试题:

⭐️ 如何自定义一个线程池,以及创建池的构造方法参数的含义?

  1. 通过创建一个ThreadPoolExecutor
  2. int corePoolSize:核心线程数,创建线程池时包含的最小线程数
  3. int maximumPoolSize:最大线程数,核心数不够用时,允许系统可以创建的最多线程
  4. long keepAliveTime:临时线程空闲的时长
  5. TimeUnit unit:空闲的时间单位,和keepAliveTime一起使用
  6. BlockingQueue<Runnable> workQueue:用来保存任务的阻塞队列
  7. ThreadFactory threadFactory:线程工厂,如何去创建线程
  8. RejectedExecutionHandler handler:拒绝策略,触发的机制:当线程池处理不了这么多任务时

⭐️ 有时候会问,创建线程池时,核心线程数多少比较合适?

  1. 这个没有一个准确的答案,要根据业务场景余计算机配置来决定
  2. 对于计算密集型(CPU执行)的程序,那么线程数可以适当调大;对于IO密集型的程序,取决于磁盘的读取效率,那么线程数过大也不会提高程序效率
  3. 还要考虑CPU得核心数量是多少
  4. 最终还是要通过测试对比,来确定一个合适的线程数

⭐️描述线程池工作原理?

image.png

  1. 当任务添加到线程池时,会先判断任务数是否大于核心任务数
  2. 如果不大于直接执行,否则假如阻塞队列
  3. 当阻塞队列满了之后,会按照指定的线程最大数创建临时线程(一次性添加)
  4. 当阻塞队列满了,临时线程也满了,就会执行拒绝策略
  5. 当任务减少,临时线程达到空间时长时,会被回收
  6. 拒绝策略

image.png

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值