面试准备 -- 学习 java 线程池

java 线程池

开发中我们经常使用的是各种框架,框架屏蔽了很多底层的东西,使得我们主要关注于业务。假设我们遇到一个业务:要返回图片和页面渲染后一起返回,如何优化?我们知道图片一般是很大的,而页面数据很小,而要等待两个组合后才返回,实在太慢了。
这时我们会想到使用多线程进行优化。但是使用多线程也出现一个问题,假设并发量很大,我们频繁的创建和销毁线程,造成资源的消耗,不仅不能提高效率,还降低了。如何解决呢?下面我们来介绍一下线程池。

java 中的 ThreadPoolExecutor 类


我们先看看线程池的实现

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize, 
                          long keepAliveTime,  
                          TimeUnit unit,   
                          BlockingQueue<Runnable> workQueue, 
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
  • corePoolSize
    核心线程池数,如果运行的线程少于这个 corePoolSize 数,那么,有任务来的时候,会创建一条新的线程去执行该任务,即使线程池中还有其他线程没事干也不会去用它们。如果线程数多于 corePoolSize 数,那么它到将于第三个参数有共同作用,多出来的线程会被销毁,直到回到最初设置的 corePoolSize 数。这个参数很重要。下面我会详细解释这参数怎么设置。

  • maximumPoolSize
    线程中中最大线程数,当 corePoolSize 的线程都在工作的时候,后续请求会进入队列中(队列在第 5 个参数,下面介绍)如果队列达到一定容量,则会创建新的线程来执行任务。这个 maximumPoolSize 参数表示线程池中最多允许创建多少个线程。

  • keepAliveTime
    线程的存活时间,这个参数只有在线程数大于 corePoolSize 的时候才会起作用。一般来说,一个线程的空闲超过 keepAliveTime 设置的时间范围内没有被使用,将会被标记为可回收,最后会被回收。

  • unit
    时间参数类 Timeunit 。里面包含了多个时间类型。具体参数如下:

TimeUnit.DAYS;         //天
TimeUnit.HOURS;        //小时
TimeUnit.MINUTES;      //分钟
TimeUnit.SECONDS;      //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS;  //纳秒

当然,对于不同的业务,需要选择不同的时间,这个因业务而定。

  • workQueue
    阻塞队列,前面稍稍讲到,后续任务会进入队列等待。实际上,我们要分情况来说。
    ① 线程数 >= corePoolSize 时,任务先进入队列中等待。如果队列也满了,则会创建新线程。
    ② 线程数 < corePoolSize 时,有任务来就直接创建新线程去执行它。

我们常用的几种队列有无界队列,有界队列和同步移交。(这里不做太多介绍),常见阻塞队列如下:

ArrayBlockingQueue  
LinkedBlockingQueue 
PriorityBlockingQueue
SynchronousQueue;   
  • threadFactory
    该参数主要的作用是创建线程。这个参数是可选参数,可以使用默认参数。默认情况下使用Executors类中的 defaultThreadFactory --默认线程工厂。我们可以定制自己的线程工厂,可以做一些操作,比如:统一线程名称,统计线程个数等。

  • handler
    定义处理被拒绝任务的策略,我们看源码可以知道,默认使用的是 ThreadPoolExecutor 中内部类 AbortPolicy 的方法,直接点就是抛异常告诉你任务被拒绝了。抛得异常是:RejectedExecutionException。可选策略如下:

    hreadPoolExecutor.AbortPolicy //拒绝任务并抛出异常

  ThreadPoolExecutor.DiscardPolicy //拒绝任务然后什么也不做

  ThreadPoolExecutor.CallerRunsPolic  //拒绝任务,重新添加该任务,该任务直接调用 execute() 方法,直到成功

  ThreadPoolExecutor.DiscardOldestPolic //找到最先被抛弃的那个任务,然后把这个任务重新入队

补充:

上面我们说到 corePoolSize 这个参数很重要。日常开发中我们也经常使用各种线程池,那到底怎么设置这个值呢?
首先,我们需要分析当前的计算环境,资源预算等,这于在部署的环境里有多少 CPU 和多大的内存也是息息相关的。
分析好后,我们将任务分为两种:IO 密集型和 CPU 密集型。
按照现在常见的线程池大小设计经验,可以参照如下设计:(以下的 N 为 CPU 个数)

  • CPU 密集型的话,线程池大小为 N + 1
  • IO 密集型的话,线程池大小为 2N + 1

对于计算密集型的任务,假设我们的电脑 CPU 的个数是 3个,那么我们的线程池大小为 4,这样通常能实现最优的利用率,即使线程出问题暂停了,我们多出的那一个线程也能保证我们 CPU 时钟周期不会被浪费。

对于 IO 密集型的任务,线程并不会一直去执行,通常都等待啥的,因此我们可以将线程池大小增大,这样可以提高利用率。

线程池是面试必问的点了,问的深还难,后续会继续深挖多线程这些知识点。

总结

文章已经同步到公众号了,欢迎关注公众号,一起学习,一起进步。祝大家学习进步,工作顺利,谢谢。
在这里插入图片描述

程序人生,与君共勉~!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值