【Java笔记】线程池

1.什么是线程池?

一次创建很多个线程,用的时候从池子里拿一个出来,用完之后还回池子

2. 为什么要用线程池?

  1. 使用线程池的核心目的是避免频繁创建和销毁线程所带来的系统开销,从而提升程序的整体性能和资源利用率。线程的创建与销毁涉及操作系统内核操作,消耗 CPU 时间和内存资源,尤其在高并发场景下,这种开销会显著降低系统效率。
  2. 线程池通过复用已有线程来执行新任务。池中的线程在初始化后会持续运行,它们并非主动“不停扫描”任务,而是阻塞等待在任务队列上:当队列中有任务时,线程立即取出并执行;当队列为空时,线程进入阻塞状态,不会占用 CPU 资源,也不会被销毁。这种机制既保证了快速响应,又避免了资源浪费。
  3. 这一思想与数据库连接池(如 DataSource)高度相似:连接池在启动时预先创建多个数据库连接,应用程序使用时从池中获取,使用完毕后归还而非真正关闭连接。线程池同样采用“借还”模式管理线程,实现了资源的高效复用和统一管理。
    在这里插入图片描述

3. 如何使用线程池?

JDK中提供了一组针对不同使用场景的线程池实例,背下来

面试题: JDK中提供了几种线程池?

public class Demo_901 {
    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 Demo_902 {
    public static void main(String[] args) throws InterruptedException {
        // 创建一个容量为 3 的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        // 向线程池中添加任务
        for (int i = 0; i < 10; i++) {
            int taskId = i + 1;
            threadPool.submit(() -> {
                System.out.println("执行任务:" + taskId + "," + Thread.currentThread().getName());
            });

            if (taskId % 2 == 0) {
                TimeUnit.SECONDS.sleep(1);
            }
        }
    }
}

执行结果:
在这里插入图片描述

这里获取线程池对象都是通过类名.方法名的方式获取对象,那么可不可以通过new的方式获取对象?
答案是可以,但是不能完整的覆盖业务的需要

举个例子:
如果要创建学生对象,一个需求是根据名字和id创建一个学生对象,另一个需求是根据名字和班级序号创建一个学生对象,但写出这两个构造方法会报错,原因是方法重载要保证参数列表的参数类型和参数个数不同,因此会报错。这个需求是真实存在的,无法这么写。

在这里插入图片描述
因此使用类名.方法名
在这里插入图片描述
这种模式也叫做工厂方法模式,根据不同的业务需求定义不同的方法来获取对象。

4. 自定义实现一个线程池

  1. 使用Runnable描述任务;
  2. 组织管理任务可以使用一个队列,可以用阻塞队列去实现;
    使用阻塞队列的好处是,当队列中没有任务的时候就等待,节省系统资源。
  3. 提供一个向队列中添加任务的方法;
  4. 创建多个线程,扫描队列中的任务,有任务的时候就取出来执行即可;

代码:

public class MyThreadPool {
    // 定义阻塞队列组织任务
    BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();

    public MyThreadPool(int threadNum) {
        if (threadNum <= 0) {
            throw new IllegalArgumentException("线程数量必须大于0");
        }
        // 创建线程
        for (int i = 0; i < threadNum; i++) {
            Thread thread = new Thread(() -> {
                // 不停扫描队列
                while (true) {
                    try {
                        // 从队列中取出任务
                        Runnable runnable = queue.take();
                        // 执行任务
                        runnable.run();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            // 启动线程
            thread.start();
        }
    }

    // 向线程池中提交任务
    public void submit (Runnable runnable) throws InterruptedException {
        if (runnable == null) {
            throw new IllegalArgumentException("任务不能为空.");
        }
        // 向队列中添加任务
        queue.put(runnable);
    }
}

调用代码:

public class Demo_904 {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool threadPool = new MyThreadPool(3);

        // 向线程池中添加任务
        for (int i = 0; i < 10; i++) {
            int taskId = i + 1;
            threadPool.submit(() -> {
                System.out.println("执行任务:" + taskId + "," + Thread.currentThread().getName());
            });

            if (taskId % 2 == 0) {
                TimeUnit.SECONDS.sleep(1);
            }
        }
    }
}

执行结果:
在这里插入图片描述

5. 创建系统自带的线程池

ThreadPoolExecutor

public class Demo_905 {
    public static void main(String[] args) throws InterruptedException {
        // 创建系统自带的线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(3, 5, 1,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(5), new ThreadPoolExecutor.CallerRunsPolicy());

        // 循环向线程池中提交任务
        for (int i = 0; i < 1000; i++) {
            int taskId = i + 1;
            threadPool.submit(() -> {
                System.out.println("执行任务:" + taskId + "," + Thread.currentThread().getName());
            });

           TimeUnit.MILLISECONDS.sleep(1);
        }
    }
}

ThreadPoolExecutor 参数及含义:

面试题:创建线程池的7个参数?

在这里插入图片描述

6. 线程池的工作原理

场景1:吃火锅
在这里插入图片描述
场景2:银行办理业务
在这里插入图片描述

7. 线程池的工作流程

  1. 添加任务,核心线程从队列中取任务去执行;
  2. 核心线程都在工作时,再添加的任务会进入到阻塞队列;
  3. 阻塞队列满了后,会创建临时线程(一次性创建最大的线程数,根据机器的配置,比如CPU核数);
  4. 临时线程不够用了之后就会执行拒绝策略

8. 拒绝策略

在这里插入图片描述

  1. 直接拒绝(会抛出异常)
    在这里插入图片描述
    比如公司给我分了个任务,我说现在手头很忙,没有时间去处理这个任务,我就告诉领导说你找别人干吧

  2. 返回给调用者
    在这里插入图片描述
    比如公司给分了个任务,我说现在手头很忙,没有时间去处理这个任务,你自己做吧;
    谁给我分配的任务我就把这个任务返回给谁,保证这个任务有线程执行;

  3. 放弃目前最早等待的任务(不会抛出异常)
    在这里插入图片描述
    比如公司给分了个任务,我说现在手头很忙,没有时间去处理这个任务,老板说最开始给你分的那个活,你可以不干了

  4. 放弃新提交的任务(不会抛出异常)
    在这里插入图片描述
    比如公司给分了个任务,我说现在手头很忙,没有时间去处理这个任务,老板说那不干就不干了吧,放弃掉

放弃的任务,以后再也找不回来了,所以指定拒绝策略的时候,要关注任务是不是需要必须执行,如果必须执行,就指定“返回给调用者”,否则1、3、4选一个即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值