一文带你清晰弄明白线程池的原理

不知道你是否还记得阿里巴巴的java代码规范中对多线程有这样一条强制规范:
【强制】线程资源必须通过线程池提供,不允许在程序中显示创建线程。
说明:使用线程池的好处是减少在创建和销毁线程池上所消耗的时间以及系统资源的开销,解决资源不足的问题,如果不适用线程池, 有可能造成系统创建大量同类线程而导致消耗完内存或者“过渡切换”的问题。

这条强制性规范也说明了使用线程池主要是解决一下两个问题:
(1)提升性能:线程池能独立负责线程的创建、维护和分配。在执行大量一部任务时,可以不需要自己创建线程,而是将任务交给线程池去调度。线程池能尽可能使用空闲的线程去执行异步任务,最大限度地对已经创建的线程进行复用,使得性能提升明显。
(2)线程管理: 每个java线程池会保持一些基本的线程统计信息, 以便对线程进行有效管理,使得能对所接收到异步任务进行高效调度。

JUC 的线程架构

JUC 是java.util.concurrent工具包的简称, 是从JDK1.5开始加入JDK的,用于完成高并发、处理多线程的一个工具包。 JUC 的线程架构如下图所示:
在这里插入图片描述
Executor是java 异步目标任务的“执行者”接口,其目的是执行目标任务。

ExecutorService继承于Executor。 它是java异步目标任务的“执行者服务接口”,对外提供异步任务的接收服务。

AbstractExecutorService 是一个抽象类,它的目的是为ExecutorService 中的接口提供默认实现。

ThreadPoolExecutor 是线程池的实现类, 是JUC线程池的核心实现类。

ScheduledExecutorService 是一个接口,可以完成延时和周期性任务调度线程池的接口,其功能类似Timer/TimerTask、

ScheduledThreadPoolExecutor提供了ScheduledExecutorService 线程池接口中“延迟执行”和周期执行等抽象调度方法的具体实现。在高并发程序中, ScheduledThreadPoolExecutor 的性能由于Timer。

Executors

Executors 是一个静态工厂类,它通过静态工厂方法返回ExecutorService、ScheduledExecutorService等线程池示例对象。 java通过Executors 工厂类可以提供4种快捷创建线程池的方法。如下图所示:
在这里插入图片描述
但是在阿里巴巴的java代码规范中,有这样一条强制规范:
【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor 的方式。
说明: Executors返回的线程池对象的弊端如下:
(1)FixedThreadPool 和SingleThreadPool 运行的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM;
(2)CacheThreadPool 和ScheduledThreadPool 允许的创建线程数量为Integer.MAX_VALUE,可能 会创建大量的线程,从而导致OOM。

那么我们使用ThreadPoolExecutor 如何创建线程池呢,

ThreadPoolExecutor创建线程池

我们通过标准构造器ThreadPoolExecutor去构造工作线程池,Executors工厂类中创建线程池的快捷工厂方法实际上是调用TheadPoolExecutor(定时任务使用ScheduledThreadPoolExecutor)线程池的构造方法完成的, 构造方法如下:

在这里插入图片描述
在这里插入图片描述
其中线程池执行器会根据corePoolSize和maximumPoolSize自动维护线程池中的工作线程,规则如下:
在这里插入图片描述
在这里插入图片描述

向线程池提交任务方式

向线程池提交任务的两种方式分别是execute()方法和submit()方法.
execute() 方法:
在这里插入图片描述

submit()方法:
在这里插入图片描述
从接口可以得出submit()和executor()方法的区别是:

  • Execute()方法只能接收Runnable类型的参数,而submit()方法可以接收Callable,Runnale两种类型的类型, Callable类型的任务是可以返回执行结果的, Runnable类型的任务不可以返回执行结果.
  • submit()提交任务后会返回值,而execute()没有
  • submit()方便Exception处理.

线程池的任务调度流程

线程池的任务调度流程如下图所示:
在这里插入图片描述
在创建线程池的是,如果线程池的参数配置不合理,就会出现任务不能被正常调度的问题.

总结:
核心和最大线程数量,BlockingQueue队列等参数如果配置得不合理,可能会造成异步任务得不到预期的并发执行, 造成严重的排队等待现象.

想城池的调度器创建线程的一条重要的规则:在核心线程数已满后,还需要等待阻塞队列已满,才会去创建新的线程.

线程池的拒绝策略

当线程池的任务缓存队列为有界队列且阻塞队列已满的情况,提交任务到线程池的时候就会被拒绝, 当线程池已经被关闭了, 提交任务到线程池也会被拒绝. 无论是哪一种情况任务被拒绝, 线程池都会调用RejectedExecutionHandler拒绝策略接口的实例的rejectedExecution方法, 拒绝策略的实现有以下5中:

  • AbortPolicy-拒绝策略: 如果线程池队列已满,新任务就会被拒绝,并且抛出RejectedExecutionException异常, 该策略是线程池默认的拒绝策略.
  • DiscardPolicy-抛弃策略: 如果线程池队列已满, 新任务就会直接被丢掉, 并且不会有任何异常抛出.
  • DiscardOldestPolicy-抛弃最老任务策略: 如果队列满了,就会将最早进入队列的任务抛弃,从队列中腾出空间,在尝试加入队列.
  • CallerRunsPolicy-调用者执行策略:在新任务被添加到线程池时,如果添加失败,那么提交任务线程会自己去执行该任务,不会使用线程池中的线程去执行新任务.
  • 自定义策略.实现RejectedExecutionHandler接口的rejectedExecution方法即可.

线程池关闭

一般情况下, 线程池启动后建议手动关闭,可以结合shutdown(),shutdownNow(),awaitTermination() 三个方法优雅地关闭一个线程池, 关闭步骤如下:
(1) 执行shutdow()方法,拒绝新任务的提交,并等待所有任务有序地执行完毕.

(2) 执行awaitTermination(Long timeout,TimeUnit unit)方法,指定超时时间,判断是否已经关闭所有任务,线程池关闭完成.

(3)如果awaitTermination()方法返回false,或者被中断,就调用shutDownNow()方法立即关闭线程池所有任务.

(4)补充执行awaitTermination(Long timeout,TimeUnit unit)方法,判断线程池是否关闭完成. 如果超时,就可以进入循环关闭,循环一定的次数,不断关闭线程池,直到其关闭或者循环结束.

关闭线程池代码如下:

public  static void shutdownThreadPoolGrecefully(ExecutorService threadPool){
        if (!(threadPool instanceof  ExecutorService)|| threadPool.isTerminated()){
            return;
        }
        try {
            //拒绝接收新任务
            threadPool.shutdown();
        }catch (SecurityException e){
            return;
        }catch (NullPointerException e){
            return;
        }
        try {
            //等待60s,等待线程池中的任务完成执行
            if(!threadPool.awaitTermination(60, TimeUnit.SECONDS)){
                //调用shutdowNow 取消正在执行的任务
                threadPool.shutdownNow();
                //再次等待60s ,如果还未结束,可以再次尝试,或则直接放弃
                if(!threadPool.awaitTermination(60,TimeUnit.SECONDS)){
                    System.out.println("线程池任务未正常完成执行结束");
                }
            }
        } catch (InterruptedException e) {
            // 捕获异常,重新调用shutdowNow
            threadPool.shutdownNow();
        }
        // 仍然没有关闭,循环关闭1000次,每次等嗲10毫秒
        if (!threadPool.isTerminated()){
            try {
                for (int i=0;i<1000;i++){
                    if (threadPool.awaitTermination(10,TimeUnit.SECONDS)){
                        break;
                    }
                    threadPool.shutdownNow();
                }
            } catch (InterruptedException e) {
                System.out.println(e.getMessage());
            }catch (Throwable e){
                System.out.println(e.getMessage());
            }
        }
    }

除了以上的关闭线程池方法以外,还可以在JVM中注册一个钩子函数,在JVM进程关闭之前,由钩子函数自动将线程池关闭,以确保资源正常释放,代码如下:

  //懒汉式单例创建线程池:用于执行定时/顺序任务
    static class SeqOrScheduledTargetThreadPoolLazyHolder{
        //线程池:用于定时任务/顺序排队执行任务
        static  final  ScheduledThreadPoolExecutor EXECUTOR= new ScheduledThreadPoolExecutor(1,new CustomThreadFactory("seq"));
        static {
            //注册JVM关闭时的钩子函数
            Runtime.getRuntime().addShutdownHook(
                    new ShutdownHookThread("定时和顺序任务线程池",
                    new Callable<Void>(){

                        @Override
                        public Void call() throws Exception {
                          //关闭线程池
                            shutdownThreadPoolGrecefully(EXECUTOR);
                            return null;
                        }
                    }
            ));
        }
    }

到此为止,线程池的原理您明白了吗?

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
操作系统是计算机系统中的核心组成部分,负责管理和协调计算机硬件和软件资源,提供程序运行环境。在CSDN上有很多关于操作系统的专题文章,以下将从操作系统的基本概念、功能和常见类型等方面简要介绍一下。 首先是操作系统的基本概念。操作系统是一种系统软件,它是计算机硬件和应用软件之间的桥梁,提供给应用程序一系列的服务和资源,同时负责调度和管理系统资源。它为用户屏蔽了底层的硬件差异,提供了一个统一的、易于使用的界面。 操作系统主要有四个基本功能。首先是处理器管理,负责将处理器分配给系统中的各个进程,并进行进程切换,实现多道程序并发执行。其次是内存管理,管理计算机的内存资源,包括分配、回收和保护等操作。再次是文件管理,负责管理文件的存储、命名和保护等操作,提供了文件操作的接口。最后是设备管理,负责管理计算机的各种设备,包括输入输出设备和存储设备等。 常见的操作系统有多种类型。最主流的是Windows、Linux和Mac OS等桌面操作系统。此外还有服务器操作系统,如Windows Server和Linux等,用于管理和部署服务器。还有嵌入式操作系统,如Android和iOS等,用于移动设备和物联网设备。操作系统也有实时操作系统,用于需要实时控制和响应的系统,如工控系统和航空航天系统等。 总之,操作系统是计算机系统中不可或缺的重要组成部分,通过CSDN上的相关文章,我们可以更深入了解操作系统的基本概念、功能和不同类型。这些知识对于理解计算机系统的工作原理和提升编程能力都有着重要意义。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

弯_弯

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

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

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

打赏作者

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

抵扣说明:

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

余额充值