• 线程池概念和用法


    什么是线程池?

    通俗理解就是一个容器,里面放了一些线程,需要用时就取出来用,用完了就放回去等待下一次用。

    线程池内部维护一个任务队列,从池里取出线程去执行队列里的任务。

    为什么要使用线程池?

    1.可以将任务的提交和执行策略解耦,便于统一管理任务执行策略,好维护,比如延时执行,设置等待时间,超时自动失败等。

    2.提高性能,用已创建的线程执行任务,减少创建和销毁线程的开销。

    3.约束最大线程并发数,防止无止境创建线程造成性能变差以及程序死掉。

    4.活跃线程数、最大线程数等参数可配置,方便进行性能调优。

    如何创建线程池?

    下图例子创建了一个核心线程数为10、最大线程数为30、非核心线程闲置超时时长为3毫秒的线程池,往线程池提交了三个任务。

    1.png

    Java线程池ThreadPoolExecutor实现了Executor接口,提供如下几个构造函数:

    2.png

    ThreadPoolExecutor构造函数各个参数的含义:

    1.核心线程:int corePoolSize => 该线程池中核心线程数最大值

    线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程

    核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。

    如果指定ThreadPoolExecutor的allowCoreThreadTimeOut这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉

    正常情况下你不干活我也养你,因为我总有用到你的时候,但有时候特殊情况(比如我自己都养不起了),那你不干活我就要把你干掉了

    2.线程池最大线程数: int maximumPoolSize 该线程池中线程总数最大值。线程总数 = 核心线程数 + 非核心线程数。

    1. 非核心线程闲置超时时长:****long keepAliveTime

    该线程池中非核心线程闲置超时时长

    一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉

    如果设置allowCoreThreadTimeOut = true,则会作用于核心线程

    4.****时间单位:unit

    指定keepAliveTime的时间单位,使用TimeUnit取值。

    3.png

    5.****工作队列:workQueue

    该线程池中的任务队列:维护着等待执行的Runnable对象

    当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务

    常用的workQueue类型:

    1. SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了maximumPoolSize而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大
    2. LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize
    3. ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误
    4. DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务

    ThreadPoolExecutor的策略

    当一个任务被添加进线程池时:

    1. 线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务
    2. 线程数量达到了corePools,则将任务移入队列等待
    3. 队列已满,新建线程(非核心线程)执行任务
    4. 队列已满,总线程数又达到了maximumPoolSize,就会由 RejectedExecutionHandler抛出异常

    上面的构造参数太多太复杂,怕玩不转?

    Concurrent包还提供了一套基于ThreadPoolExecutor的封装,使得使用者无需关注那么多配置细节,下图是Executors创建线程池的用法:

    4.png

    Executors创建的四种线程池:

    1.可缓存线程池: CachedThreadPool

    a.没有最大线程数限制,想创建多少就创建多少

    b.有空闲线程就用空闲线程,没有就创建新线程

    c.复用空闲线程能减少一部分重复创建销毁的开销

    2. 定长线程池:FixedThreadPool

    a.用来控制线程最大并发数。

    b.如果没有空闲线程,剩下的任务会在队列等待有空闲线程执行。

    3.支持定时和周期反复执行的定长线程池:ScheduledThreadPool

    4.单线程的线程池:SingleThreadExecutor

    线程池里只有一个线程,按照队列的出入规则来串行执行任务,例如先进先出。

    线程池的大小设置多少合适?

    线程池过大:大量线程竞争有限的cpu和内存资源,耗尽资源。

    线程池过小:cpu利用率低,吞吐量低。

    应当根据应用的特点来设计大小。

    1.服务器的cpu多少?内存多大?任务是计算密集型还是IO密集型还是网络请求。

    2.计算密集型的任务,在N个cpu的机器上大小为N或者N+1的线程池大小能实现最优使用率。

    3.包含IO操作的任务或者IO密集型的任务,线程不会一直执行,会等待IO响应,可以把线程池设置得比N+1更大,用监控工具估算出计算任务和等待任务的比值,然后一步步微调,一步步试,看多大是性能最优的。

    使用线程池的场景:

    1、需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。

    2、对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。

    3、接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,并出现"OutOfMemory"的错误。

    不使用线程池的场景:

    1、如果需要使一个任务具有特定优先级

    2、如果具有可能会长时间运行(并因此阻塞其他任务)的任务

    3、如果需要将线程放置到单线程单元中(线程池中的线程均处于多线程单元中)

  • 相关阅读:
    Mybatis查询select操作
    插入排序和它的进化希尔排序
    关于c头文件的使用的小记录
    Mybatis介绍
    spring tool suite开发环境搭建
    一个简单的jsp+servlet登录界面的总结
    纠正关于线程的错误理解
    表达式语言EL简单学习
    [BZOJ 1804] Flood
    [POJ 1739] Tony's Tour
  • 原文地址:https://www.cnblogs.com/powerjiajun/p/11564182.html
Copyright © 2020-2023  润新知