java线程池

线程池创建及重要参数:

       线程池可以自动创建也可以手动创建,自动创建体现在Executors工具类中,常见的可以创建newFixedThreadPool、newCachedThreadPool、newSingleThreadPool、newScheduledThreadPool。

      可以通过Executors类来创建线程池,但是不推荐。Executors类只是静态工厂,提供创建线程池的几个静态方法,而真正的线程池类是ThreadPoolExecuor。构造方法如下:

corePoolSize:核心线程数。如果等于0,则任务执行完后,没有任务请求进入时销毁线程池中的线程。如果大于0,即使本地任务执行完毕,核心线程也不会被销毁。设置过大会浪费系统资源,设置过小导致线程频繁创建。核心线程的创建是懒加载。

maximumPoolSize:最大线程数。必须大于等于1,且大于等于corePoolSize。如果与corePoolSize相等,则线程池大小固定。如果大于corePoolSize,则最多创建maximumPoolSize个线程执行任务,其他任务加入到workQueue缓存队列中,当workQueue为空且执行任务数小于maximumPoolSize时,线程空闲时间超过keepAliveTime会被回收。

keepAliveTime:线程空闲时间。线程池中线程空闲时间达到keepAliveTime值时,线程会被销毁,只到剩下corePoolSize个线程为止。默认情况下,线程池的最大线程数大于corePoolSize时,keepAliveTime才会起作用。如果allowCoreThreadTimeOut被设置为true,即使线程池的最大线程数等于corePoolSize,keepAliveTime也会起作用(回收超时的核心线程)。

unit:TimeUnit表示时间单位

workQueue:缓存队列如果当前运行的线程小于corePoolSize,则任务根本不会添加到workQueue中,如果此时运行的线程等于或大于corePoolSize时,这此时会首选将请求加入workQueue中,如果无法将请求加入workQueue(workQueue已满),则创建新的线程。如果创建线程数大于maximumPoolSize,新的任务会被拒绝。

threadFactory:线程工厂。用来生产一组相同任务的线程。主要用于设置生成的线程名词前缀、是否为守护线程以及优先级等。设置有意义的名称前缀有利于在进行虚拟机分析时,知道线程是由哪个线程工厂创建的。

handler:执行拒绝策略对象。当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务,执行拒接策略,可以看作简单的限流保护。

任务执行过程如下:

       

       假设队列大小为 10,corePoolSize 为 3,maximumPoolSize 为 6,那么当加入 20 个任务时,执行的顺序就是这样的:首先执行任务 1、2、3,然后任务 4~13 被放入队列。这时候队列满了,任务 14、15、16 会被马上执行,而任务 17~20 则会抛出异常。最终顺序是:1、2、3、14、15、16、4、5、6、7、8、9、10、11、12、13

newFixedThreadPool

       创建固定线程数的线程池核心线程数等于最大线程数,不存在空闲时间,keepAliveTime为0。

       使用的无界的阻塞队列LinedBlockingQueue,如果线程获取一个任务后,执行时间特别长,会导致队列的任务越积越多,造成OOM。

       适用于CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能少的分配线程,即适合执行长期的任务。

newSingleThreadExecutor

       创建单线程的线程池,核心线程数和最大线程数都为1。

       适用于串行执行任务的场景,一个线程一个任务一个任务的依次执行

newScheduledThreadPool

       创建支持定时以及周期性任务执行的线程池。最大线程数是Integer.MAX_VALUE。存在OOM风险。keepAliveTime为0,所有工作线程都不不回收。

       scheduleAtFixedRate() :按某种速率周期执行

        period:两次开始执行最小间隔时间

     scheduleWithFixedDelay():在某个延迟后执行

       command:要执行的任务

       initialdelay:首次执行的延迟时间

       delay: 一次执行终止和下一次执行开始之间的延迟

       unit:initialdelay 和 delay 参数的时间单位

        适用于周期性执行的场景。

newCachedThreadPool

       核心线程数为0,最大线程为Integer.MAX_VALUE,是一个高度可伸缩的线程池。存在OOM风险。keepAliveTime为60,工作线程处于空闲状态超过keepAliveTime会回收线程。

        SynchronousQueue 是一个不存储元素的队列,可以认为这个队列里永远都是满的,因此只要有一个线程任务来了就会去创建非核心线程来执行任务。

     适用在并发执行大量的短期小任务,因为空闲时间核心线程数为0,不占用CPU资源。

禁止直接使用Executors创建线程池的原因:

        Executors.newCachedThreadPool和Executors.newScheduledThreadPool两个方法最大线程数为Integer.MAX_VALUE,如果达到上限,没有任务服务器可以继续工作,肯定会抛出OOM异常。

       Executors.newSingleThreadExecutor和Executors.newFixedThreadPool两个方法的workQueue参数为new LinkedBlockingQueue<Runnable>(),容量为Integer.MAX_VALUE,如果瞬间请求非常大,会有OOM风险。

可以直接new一个ThreadPoolExecutor对象来进行指定各个线程池核心参数。

线程拒绝策略

ThreadPoolExecutor提供了四个公开的内部静态类:

AbortPolicy:默认,丢弃任务并抛出RejectedExecutionException异常。

DiscardPolicy:丢弃任务,但是不抛出异常(不推荐)。

DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中。

CallerRunsPolicy:调用任务的run()方法绕过线程池直接执行。

友好的拒绝策略:

       保存信息到数据库进行削峰填谷。在空闲时间再提出来执行。

       转向某个提示页面。

       打印日志信息。

线程池5种状态(RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED)

RUNNING

  • 该状态的线程池会接收新任务,并处理阻塞队列中的任务;
  • 调用线程池的shutdown()方法,可以切换到SHUTDOWN状态;
  • 调用线程池的shutdownNow()方法,可以切换到STOP状态;

SHUTDOWN

  • 该状态的线程池不会接收新任务,但会处理阻塞队列中的任务;
  • 队列为空,并且线程池中执行的任务也为空,进入TIDYING状态;

STOP

  • 该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;
  • 线程池中执行的任务为空,进入TIDYING状态;

TIDYING

  • 该状态表明所有的任务已经运行终止,记录的任务数量为0。
  • terminated()执行完毕,进入TERMINATED状态

TERMINATED

  • 该状态表示线程池彻底终止

线程池常用的工作队列:

ArrayBlockingQueue 数组型阻塞队列(FIFO)

  1. 初始化一定容量的数组。
  2. 使用一个重入锁,默认使用非公平锁入队和出队共用一个锁,互斥
  3. 是有界设计,如果容量无法满足继续添加直到有元素被移除(阻塞)。
  4. 使用时开辟一段连续的内存。

LinkedBlockingQueue 链表型阻塞队列(FIFO)

  1. 基于链表的阻塞队列。
  2. 吞吐量高于ArrayBlockingQueue。

DelayQueue 延时队列

  1. 一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序
  2. 只有当指定的延迟时间到了,才能从队列中获取到该元素。
  3. 因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。可以用来管理一个DelayQueue来管理一个超时未响应的连接队列。
  4. DelayQueue只能添加(offer/put/add)实现了Delayed接口的对象。

SynchronousQueue 同步队列

  1. 内部容量是0
  2. 每次删除操作都要等到插入操作
  3. 每次插入操作都要等到删除操作
  4. 一个元素,一旦有了插入和移除线程,那么很快就由插入线程移交到移除线程,这个容器相当于是通道,本身不存储元素。
  5. 在多任务队列,是最快的处理任务方式。

PriorityBlockingQueue 优先阻塞队列

  1. 具有优先级的无界阻塞队列。

面试经典题目:

    1. Java的线程池说一下,各个参数的作用,如何进行的?

    2. 按线程池内部机制,当提交新任务时,有哪些异常要考虑。

        (1)在任务代码中try/catch进行异常捕获。

        (2)通过Future对象的get方法接收跑出的异常。

        (3)重写ThreadPoolExecutor的afterExecute方法,处理传递的异常引用。

    3. 线程池都有哪几种工作队列?

    4. 使用无界队列的线程池会导致内存飙升吗?

    5. 说说几种常见的线程池及使用场景?

线程池的阻塞队列为什么都用LinkedBlockingQueue,而不用ArrayBlockingQueue?

        LinkedBlockingQueue使用单链表实现,可以指定链表长度,也可以不指定链表长度(默认为Integer.MAX_VALUE)。而且执行put的时候,将元素放到链表尾部节点;执行take的时候从头部取元素,两种操作分别有一个锁putLock和takeLock。

        ArrayBlockingQueue使用数组实现,必须指定长度。指定长度大了浪费内存,指定长度小了会造成并发性不高,put操作只能阻塞等待,或者返回false。并且ArrayBlockingQueue只定义了一个lock,put和take使用同一把锁,不能同时进行。

总结:

        1. LinkedBlockingQueue 无须指定长度,放入和取出元素使用不同的锁,双锁机制,互不影响,效率高,通用性强。

        2. ArrayBlockingQueue 必须指定长度,大了浪费内存,小了性能不高,使用同一把锁,效率低。

为什么建议在不用线程池的时候,关闭线程池?

        线程池的作用确实是为了减少频繁创建线程,使线程达到复用。但如果在不用线程池的情况下,线程池中的核心线程会一直存在浪费资源,所以建议在不用的情况下调用shutdown方法关闭线程池。在需要的时候再调用创建线程池。

参考文档:

       阿里P8大佬总结:Java线程池详解,看了你就懂

       面试必备:Java线程池解析 - Jay_huaxiao - 博客园

        使用线程池时候当程序结束时候记得调用shutdown关闭线程池 - 简书

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值