• Java并发编程的艺术笔记-Java中的线程池


    合理地使用线程池能够带来3个好处:

    • 降低资源消耗(通过重复利用已创建的线程降低线程创建和销毁造成的消耗)
    • 提高响应速度(当任务到达时,任务可以不需要等到线程创建就能立即执行)
    • 提高线程的可管理性(线程池可以进行统一分配、调优和监控线程)

    1.线程池的实现原理

    当提交一个新任务到线程池时,线程池的处理流程如下:

    • 线程池判断核心线程池里的线程是否都在执行任务:
      • 不是则创建一个新的工作线程来执行任务
      • 都在执行任务则进入下个流程
    • 线程池判断工作队列是否已经满:
      • 没满则将新提交的任务存储在这个工作队列里
      • 满了则进入下个流程
    • 线程池判断线程池的线程是否都处于工作状态:
      • 没有则创建一个新的工作线程来执行任务
      • 满了则交给饱和策略来处理这个任务

    在这里插入图片描述

    ThreadPoolExecutor执行execute方法分下面4种情况:

    • 如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(需要获取全局锁)
    • 如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue
    • 如果BlockingQueue队列已满,则创建新的线程(非核心线程)来处理任务(需要获取全局锁)
    • 如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用
      RejectedExecutionHandler.rejectedExecution()方法

    在这里插入图片描述

    什么是工作线程?线程池创建线程时,会将线程封装成工作线程Worker,Worker在执行完任务后,还会循环获取工作队列里的任务来执行


    2.线程池的使用

    2.1 线程池的创建

    通过new ThreadPoolExecutor(xxx),其中参数具体含义如下:

    • corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务(即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建,allowCoreThreadTimeOut方法可设置核心线程是否能被回收)
    • runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列(可选择Java并发容器和框架中2.2节介绍的队列)
    • maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数(如果队列满了且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务)
    • ThreadFactory:设置创建线程的工厂
    • RejectedExecutionHandler(饱和策略):当队列和线程池都满了,则采取一种策略处理提交的新任务
      • AbortPolicy(默认):直接抛出异常。
      • CallerRunsPolicy:只用调用者所在线程来运行任务。
      • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务
      • DiscardPolicy:不处理,丢弃掉且不抛出异常
    • keepAliveTime(线程活动保持时间):线程池的工作线程空闲后保持存活的时间(超过该时长,非核心线程就会被回收)
    • TimeUnit(线程活动保持时间的单位)

    2.2 向线程池提交任务

    可以使用两个方法向线程池提交任务:execute()和submit()

    • execute():用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功
    threadsPool.execute(new Runnable() {
        @Override
        public void run() {
    		...
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • submit():用于提交需要返回值的任务(返回future类型的对象,该对象可以判断任务是否执行成功且可以通过对象的get()来获取返回值)
    Future<Object> future = executor.submit(harReturnValuetask);
        try {
        	Object s = future.get();
        } catch (InterruptedException e) {
        	// 处理中断异常
        } catch (ExecutionException e) {
        	// 处理无法执行任务异常
        } finally {
        	// 关闭线程池
        	executor.shutdown();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2.3 关闭线程池

    可通过调用线程池的shutdownshutdownNow方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程

    2.4 合理地配置线程池

    可以从以下几个角度配置线程池:

    • 任务的性质:
      • CPU密集型任务:配置尽可能小的线程(Ncpu+1),因为CPU密集型任务使得CPU使用率很高,若开过多的线程数能增加上下文切换的次数,带来额外的开销
      • IO密集型任务:配置尽可能多的线程(2*Ncpu),因为CPU使用率并不高,可以让CPU在等待IO的时候去处理别的任务,充分利用CPU时间
      • 混合型任务:可将任务分成IO密集型和CPU密集型任务(两个任务执行时间相差不大时),然后分别用不同的线程池去处理
    • 任务的优先级:优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理
    • 任务的执行时间:可以交给不同规模的线程池来处理
    • 任务的依赖性:比如依赖数据库连接池的任务,线程提交SQL后需要等待数据库返回结果,等待的时间越长,CPU空闲时间就越长(相当于IO密集型任务,应设置较大线程数)

    2.5 线程池的监控

    如果在系统中大量使用线程池,则需要对线程池进行监控,在出现问题时,可根据线程池的使用状况快速定位问题。可使用以下属性:

    • taskCount:线程池需要执行的任务数量
    • completedTaskCount:线程池在运行过程中已完成的任务数量
    • largestPoolSize:线程池里曾经创建过的最大线程数量
    • getPoolSize:线程池的线程数量(线线程池不销毁的话,线程池里的线程不会自动销毁)
    • getActiveCount:获取活动的线程数
    • 重写线程池的beforeExecuteafterExecuteterminated方法进行监控

    3.已有线程池

    • 定长线程池(FixedThreadPool):用于控制线程最大并发

      • 只有核心线程
      • 线程数量固定
      • 执行完立即回收
      • 任务队列为链表结构的有界队列(消耗内存)
    • 定时线程池(ScheduledThreadPool):用于执行定时或周期性的任务

      • 核心线程数量固定
      • 非核心线程数量无限(线程过多导致内存溢出)
      • 执行完闲置10ms后回收
      • 任务队列为延时阻塞队列
    • 可缓存线程池(CachedThreadPool):执行大量且耗时少的任务

      • 无核心线程
      • 非核心线程数量无限(线程过多导致内存溢出)
      • 执行完闲置60s后回收
      • 任务队列为不存储元素的阻塞队列
    • 单线程化线程池(SingleThreadExecutor):应用于不适合并发但可能引起IO阻塞性及影响UI线程响应的操作,如数据库操作

      • 只有1个核心线程
      • 无非核心线程
      • 执行完立即回收
      • 任务队列为链表结构的有界队列(消耗内存)

    相关博客

    一个demo让你彻底理解线程池工作流程


  • 相关阅读:
    【剑指offer系列】39. 数组中出现次数超过一半的数字
    C#系统托盘功能实现
    具有交叉验证的加权向量均值算法优化核极限学习机(INFO_KELM)的回归预测MATLAB(源代码)
    露点温度介绍
    [车联网安全自学篇] Android安全之检测APK中调试代码是否暴露敏感信息
    【Linux】操作题大全
    图形处理软件Photoshop Elements 2020 mac中文版 ps简化版
    离散数学复习:二元关系
    交换机与路由器技术-09-虚拟局域网VLAN
    【前端面试题2】
  • 原文地址:https://blog.csdn.net/qq_41398418/article/details/126279652