zoukankan      html  css  js  c++  java
  • 线程池概念和用法

    什么是线程池?

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

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

    为什么要使用线程池?

    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、如果需要将线程放置到单线程单元中(线程池中的线程均处于多线程单元中)

  • 相关阅读:
    leetcode--Populating Next Right Pointers in Each Node II
    leetcode—Populating Next Right Pointers in Each Node
    Pascal's Triangle II
    leetcode—pascal triangle
    leetcode—triangle
    October 23rd, 2017 Week 43rd Monday
    October 22nd, 2017 Week 43rd Sunday
    October 21st 2017 Week 42nd Saturday
    October 20th 2017 Week 42nd Friday
    October 19th 2017 Week 42nd Thursday
  • 原文地址:https://www.cnblogs.com/powerjiajun/p/11564182.html
Copyright © 2011-2022 走看看