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() {
		...
    }
})
  • submit():用于提交需要返回值的任务(返回future类型的对象,该对象可以判断任务是否执行成功且可以通过对象的get()来获取返回值)
Future<Object> future = executor.submit(harReturnValuetask);
    try {
    	Object s = future.get();
    } catch (InterruptedException e) {
    	// 处理中断异常
    } catch (ExecutionException e) {
    	// 处理无法执行任务异常
    } finally {
    	// 关闭线程池
    	executor.shutdown();
}
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让你彻底理解线程池工作流程


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值