2024年最新Java多线程(六):线程池详解_executors(1),2024年最新C C++岗面试必问

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

3.5 单线程线程池(Executors.newSingleThreadExecutor)

3.6 根据当前CPU生成线程池(Executors.newWorkStealingPool)

3.7 手动方式(ThreadPoolExecutor)

3.7.1 创建忽略最新任务的线程池

3.7.2 ThreadPoolExecutor 参数说明

3.7.3 线程池执行流程

3.7.4 拒绝策略(5种(4(JDK提供的) + 1(自定义拒绝策略)))

4. 线程池状态

5. 究竟选用哪种线程池


1. 什么是线程池

线程池(ThreadPool)是⼀种基于池化思想管理和使用线程的机制。它是将多个线程预先存储在⼀个“池子”内,当有任务出现时可以避免重新创建和销毁线程所带来性能开销,只需要从“池子”内取出相应的线程执行对应的任务即可。

2. 为什么要使用线程池

原因有以下几条:

  1. 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
  2. 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
  3. 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统⼀的分配、调优和监控。
  4. 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池 ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。

阿里巴巴在其《Java开发手册》中也强制规定:线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

3. 线程池创建

线程池的创建方法总共有 7 种,总体来说可分为 2 类:

  1. 通过 ThreadPoolExecutor 创建的线程池;
  2. 通过 Executors 创建的线程池。

1fc2b13d56fa4b35ba94eec2565b44cd.png

线程池的创建方式总共包含以下 7 种(其中前 6 种是通过 Executors 创建的,最后 1 种是通过 ThreadPoolExecutor 创建的):

  1. Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
  2. Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后回收,若线程数不够,则新建线程;
  3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;
  4. Executors.newScheduledThreadPool:创建⼀个可以执行延迟任务的线程池;
  5. Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;
  6. Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定),根据当前CPU⽣成线程池【JDK1.8 添加】;
  7. ThreadPoolExecutor:手动创建线程池的方式,它包含了 7 个参数可供设置。

3.1 固定数量的线程池(Executors.newFixedThreadPool)

3.1.1 创建固定数量的线程池

示例代码

public class ThreadPoolDemo1 {
    public static void main(String[] args) {
        // 1.创建一个包含5个线程的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        // 2.使用线程池执行任务
        for (int i = 0; i < 5; i++) {
            // 给线程池添加任务
            threadPool.submit(() -> System.out.println("线程名称:" + Thread.currentThread().getName()));
        }

        // 2.使用线程池执行任务2
        for (int i = 0; i < 10; i++) {
            // 给线程池添加任务
            threadPool.execute(() -> System.out.println("线程名称:" + Thread.currentThread().getName()));
        }
    }
}

运行结果

ff60918f49744ea595b6bb70ec2725fa.png

3.1.2 线程池返回结果

示例代码

public class ThreadPoolDemo2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService threadPool = Executors.newFixedThreadPool(5);

        Future<Integer> future = threadPool.submit(() -> {
            int num = new Random().nextInt(100);
            System.out.println("生成随机数:" + num);
            return num;
        });
        System.out.println("得到线程池返回结果:" + future.get());
    }
}

运行结果

4e633cc3c02a465f87ea4b6ff13a3318.png

成功获得了线程池返回结果。

3.1.3 submit() VS execut()

使用线程池执行任务有两种方式:submit() 和 execut() 。这两种方式的区别如下:

9d359c818b064063b70f6f17b6347087.png

可以看到,使用 submit() 可以执行带有返回值的任务或者无返回值的任务,而 execut() 只能执行不带返回值的任务。

3.2.4 线程工厂

作用:为线程池提供现成的创建。

提供的功能

  1. 设置线程池中线程的命名规则;
  2. 设置线程优先级;
  3. 设置线程分组;
  4. 设置线程类型(守护线程 || 用户线程)。

示例代码

public class ThreadPoolDemo3 {
    public static void main(String[] args) {
        // 1.创建线程工厂
        ThreadFactory factory = r -> {
            // 一定要把任务 Runnable 设置给新线程
            Thread thread = new Thread(r);
            // 设置线程的命名规则
            thread.setName("我的线程:" + r.hashCode());
            // 设置线程的优先级
            thread.setPriority(Thread.MAX_PRIORITY);
            return thread;
        };
        ExecutorService service = Executors.newFixedThreadPool(5, factory);
        for (int i = 0; i < 5; i++) {
            service.submit(() -> {
                // 任务
                Thread thread = Thread.currentThread();
                System.out.println("线程池开始执行:" + thread.getName() + "线程池优先级:" + thread.getPriority());
            });
        }
    }
}

运行结果

f5f73caaa109477fbb5186130114c8ac.png

3.2 带缓存的线程池(Executors.newCachedThreadPool)

线程池会根据任务数创建线程,并且在一定时间内可以重复使用这些线程。

示例代码

public class ThreadPoolDemo4 {
    public static void main(String[] args) {
        // 创建线程池
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            service.submit(() -> System.out.println("i:" + finalI + " 线程名称:" + Thread.currentThread().getName()));
        }
    }
}

运行结果

46ec94f8e0684a9fb0e4dc50f984b317.png

该方式适用于短时间有且有大量任务的场景,它的缺点是可能占用很多资源。

3.3 执行定时任务(Executors.newSingleThreadExecutor)

3.3.1 延迟执行(1次)

示例代码

public class ThreadPoolDemo12 {
    public static void main(String[] args) {
        ScheduledExecutorService threadPool =
                Executors.newScheduledThreadPool(10);
        // 定时任务
        System.out.println("设置定时任务:" + new Date());
        // 延迟 n 秒后执⾏(只执⾏⼀次)
        threadPool.schedule(() -> System.out.println("schedule:" + new Date()), 2, TimeUnit.SECONDS);
    }
}

执行结果

24d99ba8b075450c9b6fbf7c3de1208f.png

延迟 2s 后执行一次。

3.3.2 固定频率执行( scheduleAtFixedRate)

示例代码

public class ThreadPoolDemo13 {
    public static void main(String[] args) {
        ScheduledExecutorService threadPool =
                Executors.newScheduledThreadPool(10);
        // 定时任务
        System.out.println("设置定时任务:" + new Date());
        threadPool.scheduleAtFixedRate(() -> 
                System.out.println("scheduleAtFixedRate:" + new Date()), 3, 2, TimeUnit.SECONDS);
    }
}

运行结果

d02c62b4d17244a99833d62de95ad1c3.png

延迟3s后执行,之后每2s执行一次。

参数解释

  1. 参数1:执行任务;
  2. 参数2:延迟 n 秒后执行;
  3. 参数3:执行定时任务的频率;
  4. 参数4:配合参数3使用的时间单位。
3.3.3 scheduleAtFixedRate VS scheduleWithFixedDelay

scheduleAtFixedRate 是以上⼀次任务的开始时间,作为下次定时任务的参考时间的(参考时间+延迟任务=任务执行)。

示例代码

public class ThreadPoolDemo5 {
    public static void main(String[] args) {
        // 创建线程池
        ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
        System.out.println("添加任务执行时间:" + LocalDateTime.now());

        // 2s之后开始执行定时任务,定时任务每隔4s执行一次
        service.scheduleAtFixedRate(() -> {
            System.out.println("执行了任务:" + LocalDateTime.now());
            try {
                Thread.sleep(5 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 2, 4, TimeUnit.SECONDS);
    }
}

运行结果

d2eaa030a21045d8abb210f898cc803c.png

2s后开始执行定时任务,每隔5s执行一次。

设置的是每隔4秒执行一次定时任务,为什么实际上是5s执行一次呢?

6c048b2483f6b0a991f2625df7ba443a.jpg

注意,如果执行任务时间大于设置的定时任务执行时间,那么此方法会以执行任务的时间为准,简而言之,就是哪个时间长就以哪个时间作为定时任务执行的周期。

scheduleWithFixedDelay 是以上⼀次任务的结束时间,作为下次定时任务的参考时间的。

示例代码

public class ThreadPoolDemo5 {
    public static void main(String[] args) {
        // 创建线程池
        ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
        System.out.println("添加任务执行时间:" + LocalDateTime.now());

        // 2s之后开始执行定时任务,每次执行间隔4秒
        service.scheduleWithFixedDelay(() -> {
            System.out.println("执行了任务:" + LocalDateTime.now());
            try {
                Thread.sleep(5 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 2, 4, TimeUnit.SECONDS);
    }
}

运行结果

31ee960d3f3f401a9898399ad76aa684.png

2s后开始执行任务,每隔9秒执行一次定时任务。

为什么这个也不是每隔4s执行一次,而是9s呢???

a9682dd3d74f6e825d0520c181230ffe.jpg

因为  scheduleWithFixedDelay 是以上⼀次任务的结束时间,作为下次定时任务的参考时间的,上个任务执行5s后,再延时4s执行延时任务。

3.4 定时任务单线程(Executors.newSingleThreadScheduledExecutor)

示例代码

public class ThreadPoolDemo6 {
    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
        System.out.println("添加任务时间:" + LocalDateTime.now());
        service.schedule(() -> System.out.println("执行任务:" + LocalDateTime.now()), 2, TimeUnit.SECONDS);
    }
}

运行结果

acd4cb63045e48f9a81563e624f37dba.png

3.5 单线程线程池(Executors.newSingleThreadExecutor)

示例代码

public class ThreadPoolDemo7 {
    public static void main(String[] args) {
        ExecutorService service = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            service.submit(() -> System.out.println("任务:" + finalI + ", 线程名:" + Thread.currentThread().getName()));
        }
    }
}

运行结果

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

int i = 0; i < 10; i++) {
int finalI = i;
service.submit(() -> System.out.println(“任务:” + finalI + “, 线程名:” + Thread.currentThread().getName()));
}
}
}


**运行结果**:




[外链图片转存中...(img-oaPMcni9-1715659203609)]
[外链图片转存中...(img-ttkhQqWY-1715659203609)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值