Java 线程池的分析和使用

线程池为线程的生命周期开销问题和资源不足问题提供了解决方案,通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。合理的利用线程池能带来很大的好处:

  • 降低资源消耗。过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

一、线程池的使用

1、线程池的创建
通过 ThreadPoolExecutor 类来创建一个线程池,所以先从他的构造函数入手:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 

以下是几个参数的说明:

  1. corePoolSize:线程池的基本大小。当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的 prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。

  2. maximumPoolSize:线程池允许创建的最大线程数。如果线程队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务,但是如果使用了无界的任务队列这个参数就没有什么效果。

  3. keepAliveTime:线程池的工作线程空闲后保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。

  4. TimeUnit:线程活动保持时间的单位。

  5. BlockingQueue:用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列:
    (1)ArrayBlockingQueue:基于数组结构的队列,按 FIFO 原则对元素进行排序。
    (2)LinkedBlockingQueue:基于链表结构的阻塞队列,按 FIFO 原则对元素进行排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法 Executors.newFixedThreadPool()使用了这个队列。
    (3)SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
    (4)PriorityBlockingQueue:一个具有优先级得无限阻塞队列。

  6. ThreadFactory:创建线程的工厂。

  7. RejectedExecutionHandler:饱和策略。当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。

2、提交任务
我们可以使用execute提交的任务,但是execute方法没有返回值,所以无法判断任务知否被线程池执行成功。execute 方法的输入是一个 Runnable 类的实例。

 executor.execute(new Runnable() {
     @Override
     public void run() {
         
     }
 });

我们也可以使用 submit 方法来提交任务,返回值是一个 Future<?>对象,我们可以通过这个对象来判断任务是否执行成功,通过 get 方法来获取返回值,get 方法会阻塞直到任务完成,而使用get(long timeout, TimeUnit unit)方法则会阻塞一段时间后立即返回,这时有可能任务没有执行完。

3、线程池的关闭
线程池的关闭可以通过调用 shutdown() 方法或shutdownNow() 方法来执行。

  • shutdown():将线程池的状态设置成 SHUTDOWN 状态,然后中断所有没有正在执行任务的线程。不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务。
  • shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务。

二、线程池分析

线程池的主要工作流程如下图:
Java线程池处理流程图
从流程图上可以看出,当提交一个任务到线程池时,线程池的处理流程如下:

  1. 首先判断基本线程池是否已满?没满,创建一个工作线程来执行任务;满了,则进入下一个流程。
  2. 其次判断工作队列是否已满?没满,则将新提交的任务存储在工作队列中;满了,则进入下个流程。
  3. 最后判断整个线程池是否已满?没满,则创建一个新的工作线程来执行任务;满了,则交给饱和策略来处理这个任务。

三、Executors 分析

阿里发布的最新的 Java 开发手册中强制线程池的使用不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这种处理方式让实现的程序员更加明确线程池的运行规则,避免资源耗尽的风险。
虽然 Executors 的方式不建议使用,但是作为面试中的常客,还是要强行分析一波 Executors 的那几个工厂方法:

1、newCachedThreadPool
创建一个可缓存线程池,如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小,但是一定要注意控制任务的数量,否则由于大量线程的同时运行,容易造成系统瘫痪。

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

该方式适用于执行很多短期异步的小程序或者负载较轻的服务器。

2、newFixedThreadPool
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

该方式适用于执行长期的任务,性能好很多。

3、newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

该方式适用于一个任务一个任务执行的场景。

4、NewScheduledThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

四、线程池调优

对于线程池的大小不能过大,也不能过小。过大会有大量的线程咋相对较少的CPU和内存上竞争,过小又会导致空闲的处理器无法工作,浪费资源,降低吞吐率。所以如何合理的给出线程池的大小非常的重要。
对于线程池的大小设定,一般要考虑CPU个数,内存大小,任务类型等问题。有种简单的估算方式,设N为CPU个数:

  • 对于CPU密集型的应用,线程池的大小设置为N+1。
  • 对于I/O密集型的应用,线程池的大小设置为2N+1 。

这种设置方式适合于一台机器上的应用的类型是单一的,并且只有一个线程池,实际情况还需要根据实际的应用进行验证。

在I/O优化中,以下的估算公式可能更合理
最佳线程数量 = ((线程等待时间+线程CPU时间)/ 线程CPU时间)* CPU个数

由公式可得,线程等待时间所占比例越高,需要越多的线程。
线程CPU时间所占比例越高,所需的线程数越少。

参考:如何合理确定线程池大小

内容概要:本文围绕含氢气氨气的综合能源系统优化调度展开研究,提出了一种基于Matlab的仿真建模与优化方法,旨在实现多能互补、高效利用与低碳运行。研究构建了包含风能、太阳能、电解水制氢、氢气储存、氢合成氨、氨储存及能源转换设备在内的综合能源系统架构,重点考虑了氢、氨作为二次能源载体在能量存储与转化中的关键作用。通过建立系统各组件的数学模型,如电解槽效率模型、合成氨反应动力学模型、储氢储氨容量模型等,并结合可再生能源出力不确定性、负荷需求波动等因素,构建了以系统运行成本最小化、碳排放最小化或多目标综合最优为目标的优化调度模型。采用智能优化算法(如改进粒子群算法、多目标优化算法等)对模型进行求解,实现了对系统中各类设备出力、储能充放电状态、能量交互功率等变量的精细化调度,有效提升了能源利用效率与系统经济性。; 适合人群:具备一定电力系统、能源工程或自动化专业背景,熟悉Matlab/Simulink仿真工具,从事新能源、综合能源系统、氢能等领域研究的研发人员、研究生及高年级本科生。; 使用场景及目标:① 为含氢、氨等新型能源载体的综合能源系统规划设计提供理论依据技术支撑;② 实现对风光等波动性可再生能源的高效消纳,提高系统灵活性与可靠性;③ 通过优化调度降低系统运行成本与碳排放强度,服务于“双碳”战略目标。; 阅读建议:此资源以Matlab代码实现为核心,提供了完整的仿真模型与优化算法代码,学习者应结合相关专业知识,深入理解模型构建的物理意义与数学表达,调试并运行代码以掌握其工作流程,进而可根据实际需求对模型进行扩展与改进。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值