Java 深入浅出线程池

文章目录


前言

对Java线程池的学习,予以记录!

一、Java线程池基本概念

1、为什么要使用线程池?

降低了之前通过new Thread对象频繁创建与销毁线程所带来的资源损耗

2、线程池是干嘛的?

主要用来控制运行的线程的数量,处理过程中将任务放入队列中,然后在线程创建后启动这些任务,如果线程的数量超过了最大数量的线程则排队等候,等其他线程执行完毕,再从队列中取出任务来执行

3、使用线程池有什么优势?

①线程复用

降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗

②控制最大并发数

提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行

③管理线程

提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控

二、Java线程池如何使用?

1、架构说明

Java中的线程池是通过Executor框架实现的,该框架用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类
在这里插入图片描述

2、编码实现

五种从线程池创建线程方式(1,2了解,3,4,5掌握)

通过分别调用Executors类的方法实现不同类型功能的线程池,但是底层仍然是调用了ThreadPoolExecutor

// 创建一个定长线程池,支持定时及周期性任务执行。
ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
// java8新出
ExecutorService executor = Executors.newWorkStealingPool();
// 适用:执行长期的任务,性能好很多。1线程池5个处理线程
ExecutorService executor = Executors.newFixedThreadPool(5);
// 适用:一个任务一个任务的执行的场景。1线程池1个处理线程
ExecutorService executor = Executors.newSingleThreadExecutor();
// 适用:执行很多短期异步的小程序或者负载较轻的服务器。1线程池N个处理线程
ExecutorService executor = Executors.newCachedThreadPool();

①Executors.newScheduledThreadPool(常驻核心线程数)

创建一个定长线程池,支持定时及周期性任务执行。延迟执行

在这里插入图片描述
在这里插入图片描述

②Executors.newWorkStealingPool()

java8新特性,创建一个具有抢占式操作的线程池ForkJoinPool 类

在这里插入图片描述
在这里插入图片描述

③Executors.newFixedThreadPool(线程数)

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

在这里插入图片描述

④Executors.newSingleThreadExecutor()

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

在这里插入图片描述

⑤Executors.newCachedThreadPool()

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

在这里插入图片描述

阻塞队列的学习!!!

3、Demo:线程池的简单使用

public class ThreadPoolExecutorDemo {
    /*10个人在银行办事,银行有五个工作人员*/
    public static void main(String[] args) {
//        ExecutorService executor = Executors.newFixedThreadPool(5);
//        ExecutorService executor = Executors.newSingleThreadExecutor();
        ExecutorService executor = Executors.newCachedThreadPool();
        try {
            for (int i = 1; i <= 10; i++) {
                int finalI = i;
                executor.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t" + finalI);

                });
            }
        }
        finally {
            executor.shutdown();
        }
    }
}

结果:不唯一

在这里插入图片描述

三、Java线程池的七个重要参数

在这里插入图片描述
在这里插入图片描述

1、corePoolSize:线程池中的常驻核心线程数

表示线程池核心线程数,当初始化线程池时,会创建核心线程进入等待状态,即使它是空闲的,核心线程也不会被摧毁,从而降低了任务一来时要创建新线程的时间和性能开销。

  • 在创建了线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务,近似理解为今日当值线程
  • 在线程池中的线程数达到corePoolSize后,就会把到达的任务放到缓存队列中

2、maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1

表示最大线程数,意味着核心线程数都被用完了,那只能重新创建新的线程来执行任务,但是前提是不能超过最大线程数量,否则该任务只能进入阻塞队列进行排队等候,直到有线程空闲了,才能继续执行任务。

3、keepAliveTime:多余的空闲线程的存活时间

表示线程存活时间,除了核心线程外,那些被新创建出来的线程可以存活多久。意味着,这些新的线程一但完成任务,而后面都是空闲状态时,就会在一定时间后被摧毁。

4、unit:keepAliveTime的单位

存活时间单位。

5、workQueue:任务队列,被提交但尚未被执行的任务

表示任务的阻塞队列,由于任务可能会有很多,而线程就那么几个,所以那么还未被执行的任务就进入队列中排队,队列我们知道是 FIFO 的,等到线程空闲了,就以这种方式取出任务。这个一般不需要我们去实现。

6、threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程,一般用默认的即可

7、handler:拒绝策略,表示当前队列满了并且工作线程大于等于线程池的最大线程数(maxmumPoolSize)

等待队列已经排满了,再也塞不下新任务了,同时,线程池中的线程数量也达到了maximumPoolSize,无法继续为新任务服务,此时我们就需要拒绝策略机制合理的处理这个问题

JDK内置的四种拒绝策略均实现了RejectedExecutionHandler接口

在这里插入图片描述

①AbortPolicy(默认)

直接抛出RejectedExecutionException异常阻止系统正常运行

②CallerRunsPolicy

“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者

③DiscardOldestPolicy

抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务

④DiscardPolicy

直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案

四、线程池底层原理与处理流程

1、线程池底层是什么?

ThreadPoolExecutor、ThreadPoolExecutor、ThreadPoolExecutor

重要的事情说三遍!!!

除WorkStealingPool底层是具有抢占式操作的ForkJoinPool类,其余四种线程池ScheduledThreadPool,FixedThreadPool,SingleThreadExecutor以及CachedThreadPool的底层都是ThreadPoolExecutor
在这里插入图片描述

2、线程池处理流程

在这里插入图片描述

  1. 在创建了线程池后,等待提交过来的任务请求。
  2. 当调用execute()方法添加一个请求任务时,线程池会做如下判断:
    • 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
    • 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列
    • 如果这时候队列满了且正在运行的线程数量还小于maximumPooSize,那么还是要创建非核心线程立刻运行这个任务;
    • 如果队列满了且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
  3. 当一个线程完成任务时,它会从队列中取下一个任务来执行。
  4. 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程池会判断:
    • 如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。
    • 所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小。

五、自定义线程池

1、为什么要自定义线程池?

因为线程资源必须通过线程池提供,不允许在应用中自行显式创建线程(new Thread().start())

2、为什么不用JDK帮助我们已经写好的Executor类的new方法?

一个都不用,生产中我们只使用自己定义的 生产实践与知识教育并不能一概而论

线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险:
- newFixedThreadPool和newSingleThreadExecutor:允许的LinkedBlockingQueue请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
- newCachedThreadPool和newScheduledThreadPool:允许创建的线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM

参考:JAVA 阻塞队列BlockingQueue接口

3、Demo验证:线程池的处理流程及拒绝策略

public static void main(String[] args) {
    ExecutorService threadPool = new ThreadPoolExecutor(
            2,
            5,
            1L,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(3),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());
    try {
        for (int i = 1; i <= 5; i++) {
            threadPool.execute(() -> {
                System.out.println("线程" + Thread.currentThread().getName() + "\t办理业务");
            });
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        threadPool.shutdown();
    }
}

循环次数等于corePoolSize + 阻塞队列长度时,不创建新的线程:

在这里插入图片描述

将循环次数改为8,大于corePoolSize + 阻塞队列长度且线程数小于maximumPoolSize时,需要创建新的线程:

在这里插入图片描述

循环次数改为9,大于maximumPoolSize + 阻塞队列长度时:

  • 在AbortPolicy拒绝策略下出现RejectedExecutionException异常:

在这里插入图片描述

  • 在CallerRunsPolicy拒绝策略下出现调用调用者线程运行的现象:

在这里插入图片描述

  • 在DiscardOldestPolicy拒绝策略下,抛弃队列中等待最久的任务。当循环次数改为更大时,任务数量始终保持maximumPoolSize + 阻塞队列长度:

在这里插入图片描述

  • 在DiscardPolicy拒绝策略下,直接丢弃任务,不予任何处理也不抛出异常。当循环次数改为更大时,任务数量始终保持maximumPoolSize + 阻塞队列长度:

在这里插入图片描述

4、总结

  1. 处理流程
    • 线程池可处理的任务数 = 最大线程数 + 阻塞队列长度
    • 当创建线程到最大线程数后,非核心线程数优先处理不在阻塞队列中的任务。
    • 任务数等于corePoolSize + 阻塞队列长度时,不创建新的线程
    • 任务数大于corePoolSize + 阻塞队列长度且线程数小于maximumPoolSize时,需要创建新的线程
    • 任务数大于corePoolSize + 阻塞队列长度且线程数大于maximumPoolSize时,采取相应的任务拒绝策略
1.当正在运行的线程数小于常驻核心线程数时,那么会立即创建线程执行任务;

2.当正在运行的线程数大于或者等于核心线程数,那么将这个任务放入队列中;

3.当队列已满且正在运行的线程数小于最大线程数,那么还是要创建非核心线程立刻运行非队列中的任务;

4.当队列满了,且运行线程数大于或者等于最大线程数,那么就启用饱和拒绝策略来执行
  1. 拒绝策略
    • 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时,如果还有任务到来就会采取任务拒绝策略
四大拒绝策略作用
ThreadPoolExecutor.AbortPolicy丢弃任务并抛出RejectedExecutionException异常
ThreadPoolExecutor.CallerRunsPolicy由调用线程(提交任务的线程)处理该任务
ThreadPoolExecutor.DiscardOldestPolicy丢弃队列等待最久的的任务,然后重新提交当前任务(任务数为maximumPoolSize + 阻塞队列长度)
ThreadPoolExecutor.DiscardPolicy直接丢弃任务,不予任何处理也不抛出异常(任务数为maximumPoolSize + 阻塞队列长度)

六、如何合理的配置线程池参数,比如最大线程数?

1、查看当前电脑配置–>CPU核心线程数

// 获取cpu核心线程数(计算资源)询问jvm,jvm去问操作系统,操作系统去问硬件
System.out.println(Runtime.getRuntime().availableProcessors());

我的笔记本CPU核心线程数是20
在这里插入图片描述

2、根据具体业务情况分析,CPU密集或IO密集

①CPU密集,CPU核心线程数+1个线程的线程池

在这里插入图片描述

也就是说,我应该设置最大线程数为20+1=21

②IO密集,CPU核心线程数*2 或 CPU核心线程数/(1-阻塞系数)

设置最大线程数一般根据业务压测出来

在这里插入图片描述
在这里插入图片描述

也就是说,我可以设置最大线程数为20*2=40或者20/(1-0.9)=200【取乐观阻塞系数,频繁IO操作】
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
很抱歉,我无法回答关于"深入浅出Java虚拟机PDF"的问题。但是,我可以告诉你关于Java虚拟机的一些信息。Java虚拟机(JVM)是Java程序的运行环境,它负责将Java字节码转换为机器语言并执行程序。通过使用不同的虚拟机参数,可以对JVM进行配置和调优。 通过设置-Xss参数,可以指定每个线程的Java虚拟机栈内存大小。在JDK 1.4中,默认为256K,在JDK 1.5及更高版本中,默认为1M。例如,使用java -Xss2M命令可以将线程栈内存大小设置为2M。 Java虚拟机使用ClassLoader加载类。所有其他类的加载器都是Java实现的,并继承自抽象类java.lang.ClassLoader。 当老年代空间不足时,可能会触发Full GC。但是,并不建议使用这种方式来管理内存,而是让虚拟机自行管理。因为手动执行Full GC可能会导致性能问题。 希望以上信息对你有所帮助。如果你对Java虚拟机还有其他问题,请随时提问。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [深入浅出JAVA虚拟机](https://blog.csdn.net/loulanyue_/article/details/101075303)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值