线程池超详细介绍

Table of Contents

一、线程池的优势?

二、四个构造方法

三:线程池参数解释及工作流程

四、参数合理配置

五、类层级关系

六、创建线程池一个池并分析实例?       

七、线程池的正确选择

八、Java中的几种阻塞队列


一、线程池的优势?

  1. 重用存在的线程。减少线程创建、消亡的开销,提高性能
  2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行
  3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控

 

二、四个构造方法

ThreadPoolExecutor类是创建线程池的核心类

介绍四种构造方法

 

三:线程池参数解释及工作流程

corePoolSize核心线程数大小,队列没满时,只会创建和使用核心线程数大小
maximumPoolSize非核心线程数(maximumPoolSize-corePoolSize),也叫最大线程数,队列满了之后还有需要入队的产品,就会创建非核心线程来使用,产品出队列给非核心线程工作,需要入队的产品继续入队
keepAliveTime超时时间大小设置。非核心线程超过这个时间,就会被回收,被视为没有用线程(核心线程默认不会被回收)
unit

设置超时时间单位。有

TimeUnit.DAYS; //天

TimeUnit.HOURS; //小时

TimeUnit.MINUTES; //分钟

TimeUnit.SECONDS; //秒

TimeUnit.MILLISECONDS; //毫秒,常用

TimeUnit.MICROSECONDS; //微妙

TimeUnit.NANOSECONDS; //纳秒

workQueue线程阻塞队列选择,常用有ArrayBlockingQueue、LinkedBlockingQueue
threadFactroy创建线程的方式,工厂。可以用默认的,也可以apache和guava等。下面介绍
handler

拒绝策略,当阻塞队列满时,工作线程又已经达到最大数,此时选择采用拒绝策略来应对这种情况,默认是抛出异常这种拒绝策略。

 1>ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

 2>ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。

 3>ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

 4>ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

allowCoreThreadTimeOut控制是否允许核心线程超时退出。设置为true运行核心线程数超时被回收,默认为false
poolSize线程池中当前线程的数量,当该值为0的时候,意味着没有任何线程,线程池会终止;同一时刻,poolSize不会超过maximumPoolSize

例子说话:

当corePoolSize:5  maximumPoolSize:6 workQueue:5的时候,分析

threadFactroy是一个老板。有一个小仓库(阻塞队列),可以存放5个产品。

场景1:产品到了3个,不往仓库丢,老板直接召了3名职工(corePoolSize)负责完成产品包装。

场景2:产品到了5个,不往仓库丢,老板直接召了5名职工(corePoolSize)负责完成产品包装。

场景3:产品到了6个,往仓库丢一个,老板召了5名职工(corePoolSize)负责完成产品包装。剩下一个等待第一个职工完成后由它来负责

场景4:产品到了11个,往仓库丢5个,老板召了5名职工(corePoolSize)负责完成产品包装。发现还有一个产品没有放下,仓库满了,此时老板用最后的资金又召了一名职工(maximumPoolSize=5+1=6),此时从仓库里拿出一个产品给这个职工包装,还有一个产品放入仓库。

场景5:产品到了12个,往仓库丢5个,老板召了6名职工(maximumPoolSize=6),发现还有一个产品没有放下仓库,仓库已满,职工又全部在工作中,老板也没有资金召职工,此时采用拒绝策略来处理。

场景6:产品到了11个,往仓库丢5个,老板召了5名职工(corePoolSize)负责完成产品包装。发现还有一个产品没有放下,仓库满了,此时老板用最后的资金又召了一名职工(maximumPoolSize=5+1=6),此时从仓库里拿出一个产品给这个职工包装,还有一个产品放入仓库。当时间已经过了一个月(keepAliveTime)时,产品数都是在1-10个之间,最后召了那名职工没事做,此时老板将它开掉,节约薪资。

上述例子对应逻辑:

1、如果当前线程池的线程数还没有达到基本大小(poolSize < corePoolSize),无论是否有空闲的线程新增一个线程处理新提交的任务;

2、如果当前线程池的线程数大于或等于基本大小(poolSize >= corePoolSize) 且任务队列未满时,就将新提交的任务提交到阻塞队列排队,等候处理workQueue.offer(command);

3、如果当前线程池的线程数大于或等于基本大小(poolSize >= corePoolSize) 且任务队列满时

3.1、当前poolSize<maximumPoolSize,那么就新增线程来处理任务;

3.2、当前poolSize=maximumPoolSize,那么意味着线程池的处理能力已经达到了极限,此时需要拒绝新增加的任务。至于如何拒绝处理新增的任务,取决于线程池的饱和策略RejectedExecutionHandler。

 

四、参数合理配置

(1)CPU密集型
       CPU密集型的意思就是该任务需要大量运算,而没有阻塞,CPU一直全速运行。

       CPU密集型任务只有在真正的多核CPU上才可能得到加速(通过多线程)。

       CPU密集型任务配置尽可能少的线程数。

       CPU密集型线程数配置公式:CPU核数+1个线程的线程池

 

(2)IO密集型
       IO密集型,即该任务需要大量的IO,即大量的阻塞。大量的文件操作。

       在单线程上运行IO密集型任务会导致浪费大量的CPU运算能力浪费在等待。

       所以IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要利用了被浪费掉的阻塞时间。

      

       第一种配置方式:

       由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程。

       配置公式:CPU核数 * 2。

       第二种配置方式:

       IO密集型时,大部分线程都阻塞,故需要多配置线程数。

       配置公式:CPU核数 / 1 – 阻塞系数(0.8~0.9之间)

       比如:8核 / (1 – 0.9) = 80个线程数

例如:我的电脑是8核,逻辑核是16核,其实最多就是16个线程可以开

下面配置了一个线程池如下(核心线程池配置的就是16,逻辑处理器核数)

 

五、类层级关系

 

六、创建线程池一个池并分析实例?       

ExecutorService executorService = new ThreadPoolExecutor(5, 6, 10L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory());

        for (int i = 0; i < 11; i++) {
            executorService1.execute(new Thread() {
                @Override
                public void run() {
                    System.out.println("aaa");
                    try {
                        sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        Thread.sleep(200);

        System.out.println( ((ThreadPoolExecutor) executorService).getActiveCount());
        executorService.shutdown();

代码解析:创建5个corePool核心线程、1个非核心线程(maximumPoolSize=1+5=6)、5个ArrayBlockingQueue<>有界队列,非核心线程过时策略是10毫秒被回收,采用默认工厂场景线程池。

当有11个任务到来时(这里11个线程理解成11个产品或任务),执行情况分析如下:

运行结果(输出6个活跃线程,说明非核心线程也参与了工作)

当有12个产品/线程到来时,启动默认拒绝策略,抛出异常

当有10个产品时,不会使用非核心线程数,因为队列刚好放的下核心线程未处理剩下的产品

 

 

七、线程池的正确选择

由Executors类来创建线程池默认不推荐,可能会造成oom异常,创建方式很多,如下

但是这些场景方式,默认的阻塞队列长度是Integer.MAX_VALUE,就是2^32-1

阿里巴巴都不推荐使用!!!

 

创建线程池的正确方式:

避免使用Executors创建线程池,主要是避免使用其中的默认实现,那么我们可以自己直接调用ThreadPoolExecutor的构造函数来自己创建线程池。在创建的同时,给BlockQueue指定容量就可以了,也可以通过设置自己的工厂来创建线程。

创建线程的工厂都由自己指定,使用apache和guava等,这里没有他们的maven依赖,搞不定。。


八、Java中的几种阻塞队列

一般使用ArrayBlockingQueueLinkedBlockingQueue设置队列大小

参考博客:https://blog.csdn.net/gaotiedun1/article/details/86606135

 

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MFC(Microsoft Foundation Class)是一套用于开发Windows桌面应用程序的C++类库。MFC提供了许多功能和工具,包括线程池,用于处理并发执行的任务。下面是一个使用MFC线程池详细示例: 1. 首先,在你的MFC应用程序中,包含头文件 afxmt.h,该文件定义了MFC中线程池相关的类和函数。 2. 创建一个继承自CWinApp的应用程序类,并在构造函数中调用SetAppCompatiblityMode函数,以确保应用程序与线程池兼容。示例代码如下: ```cpp class CMyApp : public CWinApp { public: CMyApp() { SetAppCompatiblityMode(_WIN32_WINNT_WIN7); } // ... }; ``` 3. 在你的主窗口类中,添加一个成员变量用于表示线程池,并在窗口的创建过程中初始化该线程池。示例代码如下: ```cpp class CMyWnd : public CFrameWnd { private: CThreadPool m_threadPool; public: CMyWnd() { m_threadPool.Initialize(); } // ... }; ``` 4. 在需要使用线程池的地方,创建一个继承自CWorkerThread的工作线程类,并重写其Run方法,该方法定义了线程需要执行的任务。示例代码如下: ```cpp class CMyWorkerThread : public CWorkerThread { public: virtual DWORD Run() { // 执行任务的代码 // ... return 0; } }; ``` 5. 在主窗口类中,创建并启动工作线程。示例代码如下: ```cpp void CMyWnd::StartWorkerThread() { CMyWorkerThread* pWorkerThread = new CMyWorkerThread(); m_threadPool.AddWorkerThread(pWorkerThread); pWorkerThread->Run(); } ``` 通过以上步骤,你就可以在MFC应用程序中使用线程池来处理并发任务了。线程池会自动管理线程的创建、销毁和调度,你只需要定义好工作线程类的任务代码即可。 注意:以上示例代码仅为演示用途,实际使用中需要根据具体需求进行适当的修改和扩展。另外,MFC线程池的使用还涉及更多细节,如任务队列、线程同步等,你可以参考MFC文档和相关教程来深入学习和理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值