JAVA后端开发面试基础知识(七)——多线程

本文详细介绍了JAVA后端开发面试中关于多线程的知识点,包括线程池原理及其核心参数,Callable、Future、FutureTask的使用与区别,Synchorized与ReentrantLock的对比,Volitate的原理与应用,ThreadLocal的作用,以及并发的三大性质。此外,还探讨了高并发解决方案、不同类型的BlockingQueue,线程的创建与操作方式,以及sleep、wait、start、run的区别。文章最后讨论了Synchronized的实现原理和锁优化策略,以及CAS操作和ABA问题,以及ReentrantLock和AQS在并发控制中的应用。
摘要由CSDN通过智能技术生成

1. 线程池原理

  • 优点

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

    • corePoolSize:线程池核心线程数量
    • maximumPoolSize:线程池最大线程数量
    • keepAliverTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间
    • unit:存活时间的单位
    • workQueue:存放任务的队列
    • handler:超出线程范围和队列容量的任务的处理程序
  • 线程处理流程

    1. 当线程池中线程数量小于 corePoolSize 则创建线程,并处理请求。
    2. 当线程池中线程数量大于等于 corePoolSize 时,则把请求放入 workQueue 中,随着线程池中的核心线程们不断执行任务,只要线程池中有空闲的核心线程,线程池就从 workQueue 中取任务并处理。
    3. 当 workQueue 已存满,放不下新任务时则新建非核心线程入池,并处理请求直到线程数目达到 maximumPoolSize(最大线程数量设置值)。
    4. 如果线程池中线程数大于 maximumPoolSize 则使用 RejectedExecutionHandler 来进行任务拒绝处理。

    ThreadPoolExecutor的execute()方法

    addIfUnderCorePoolSize(command)

  • 饱和策略RejetedExecutionHandler

    • AbortPolicy:直接抛出异常
    • CallerRunsPolicy:只用调用所在的线程运行任务
    • DiscardOldestPolicy:抛弃线程池中工作队列头部的任务(即等待时间最久的任务), 并执行新传入的任务。
    • DiscardPolicy:不处理,丢弃掉。
  • 线程池类型

    • SingleThreadExecutor:单线程线程池

      里面的核心线程数和线程数都是1,并且工作队列使用的是无界队列。由于是单线程工作,每次只能处理一个任务,所以后面所有的任务都被阻塞在工作队列中,只能一个个任务执行。

    • FixedThreadExecutor:固定大小线程池

      LinkedBlockingQuene,这个队列是无界的,若消费不过来,会导致内存被任务队列占满,最终oom

    • CachedThreadPool:无界线程池

      无界线程池意味着没有工作队列,任务进来就执行,线程数量不够就创建,与前面两个的区别是:空闲的线程会被回收掉,空闲的时间是60s。这个适用于执行很多短期异步的小程序或者负载较轻的服务器。

      初始化一个可以缓存线程的线程池,默认缓存60s,线程池的线程数可达到Integer.MAX_VALUE, 即2147483647,内部使用SynchronousQueue作为阻塞队列;和newFixedThreadPool创建的线程池不同,newCachedThreadPool在没有任务执行时,当线程的空闲时间超过keepAliveTime, 会自动释放线程资源,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定 的系统开销,因为线程池的最大值了Integer.MAX_VALUE,会导致无限创建线程;所以,使用该线程池时,一定要注意控制并发的任务数,否则创建大量的线程会导致严重的性能问题。

    • newScheduledThreadPool

      创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

  • 创建线程的几种方法

    • ThreadPoolExecutor
    • ThreadScheduledExecutor
    • ForkJoinPool
  • 线程池大小设置

    • CPU密集型

      • 尽量使用较小的线程池,一般Cpu核心数+1

        因为CPU密集型任务CPU的使用率很高,若开过多的线程,只能增加线程上下文的切换次数,带来额外的开销

    • IO密集型

      • 可以使用较大的线程池,一般CPU核心数 * 2

        IO密集型CPU使用率不高,可以让CPU等待IO的时候处理别的任务,充分利用cpu时间

      • 线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。

        下面举个例子: 比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核 心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。这个公式进一步转化为:

    最佳线程数目=(线程等待时间与线程CPU时间之比+ 1)CPU数目

    • 混合型

      • 可以将任务分为CPU密集型和IO密集型,然后分别使用不同的线程池去处理,按情况而定
  • 风险

    • 死锁
    • 资源不足
    • 并发错误
    • 线程泄漏
    • 请求过载

2. Callable、Future、FutureTask

  • Cabllable
  • Future
    • future.get方法会阻塞当前线程, 直到任务执行完成返回结果为止。
  • FutureTask
    • 可取消的异步计算,此类提供了对Future的基本实现,仅在计算完成时才能获取结果,如果计算尚未完 成,则阻塞get方法。
    • FutureTask不仅实现了Future接口,还实现了Runnable接口,所以不仅可以将FutureTask当成一个 任务交给Executor来执行,还可以通过Thread来创建一个线程。
  • 三者之间的关系:
    • Callable是Runnable封装的异步运算任务。
    • Future用来保存Callable异步运算的结果
    • FutureTask封装Future的实体类
  • Callable与Runnbale的区别
    • Callable定义的方法是call,而Runnable定义的方法是run。
    • call方法有
  • 16
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

达分柒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值