线程的创建(线程池)

线程创建的四种方法以及特点:
1.直接new Thread 一般不使用该方法
缺点:
1)、每次new Thread新建对象性能差。
2)、线程缺乏统一管理,可能无限制新建线程,相互之间竞争及可能占用过多系统资源导致死机或oom。
3)、缺乏更多功能,如定时执行、定期执行、线程中断。
2.实现runnable接口
优点:没有返回值的时候使用特别方便
缺点:没有返回值,也不能抛异常
3.实现Callable和Future接口
Java针对Future接口提供的具体实现类FutureTask
实际调用的是Callable中的call方法,而Callable中没有直接操作runnable的方法,
只能通过Future中的get方法来间接实现对runnable中run方法的调用。
优点:有返回值,可以抛出异常。

4.使用线程池来创建线程 适用于创建多个线程
使用ExecutorService、Callable、Future实现有返回结果的线程,线程池的具体实现实际上是依赖于ThreadPoolExecutor
优点:
1)重用存在的线程,减少对象创建、消亡的开销,性能佳
2)可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞
3)提供定时执行、定期执行、单线程、并发数控制等功能。
缺点:
不管使用与否,线程池都会创建,可能会浪费资源

线程的创建(线程池)_第1张图片

线程池和并行处理

    • Spring框架中还有一个补充
  • 线程是稀缺资源,如果被无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,合理的

  • 使用线程池对线程进行统一分配、调优和监控,

  • 有以下好处:1、降低资源消耗;2、提高响应速度;3、提高线程的可管理性。

  • Java1.5中引入的Executor框架把任务的提交和执行进行解耦,只需要定义好任务,然后提交给线

  • 程池,而不用关心该任务是如何执行、被哪个线程执行,以及什么时候执行。

    • 线程池可以节约系统资源,包括线程、内存资源等,这样可以避免创建过多的线程导致线程资源匮
  • 乏、系统频繁进行上下文切换以及内存溢出等问题,因为线程池中的每一个线程可能会轮询地执行多个任务

    • 线程池可以节省重新创建线程的时间,进而提高响应速度。
  • ExecutorService是Java中对线程池定义的一个接口,它java.util.concurrent包中。Java API

  • 对ExecutorService接口的实现有两个(ThreadPoolExecutor和ScheduledThreadPoolExecutor)

  • ,所以这两个即是Java线程池具体实现类。除此之外,ExecutorService还继承了Executor接口(

  • 注意区分Executor接口和Executors工厂类),这个接口只有一个execute()方法

  • Executors只是一个工厂类,它所有的方法返回的都是ThreadPoolExecutor、

  • ScheduledThreadPoolExecutor这两个类的实例

  • Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一

  • 个执行线程的工具。真正的线程池接口是ExecutorService。

  • 线程池的作用:

    • 线程池作用就是限制系统中执行线程的数量
    • 根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。
  • 为什么要用线程池:

    • 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
    • 可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为因为消耗过多的内存,而把
  • 服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)

  • 系统预定义的线程池:

  • 1、 可缓存线程池

  • newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收

  • 空闲线程,若无可回收重用时则新建线程。

  • public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
    new SynchronousQueue());
    }

    • 工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger.MAX_VALUE), 这样
  • 可灵活的往线程池中添加线程。

    • 如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),
  • 则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线

  • 程。

    • 在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,
  • 很有会造成系统瘫痪。

  • 特点:

    • 重用之前的线程
    • 适合执行许多短期异步任务的程序。
    • 调用execute() 将重用以前构造的线程
    • 如果没有可用的线程,则创建一个新线程并添加到池中
    • 默认为60s未使用就被终止和移除
    • 长期闲置的池将会不消耗任何资源
  • 2、 定长线程池

  • newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

    • newFixedThreadPool可控制线程最大并发数,当线程池中的线程数达到其设定大小时,其余
      新创建的线程会在LinkedBlockingQueue队列中等待
    • 当线程池中的某个线程失败而终止时,新的线程会代替它执行剩下的任务
    • 线程池中的线程只有在显式调用shutdown函数时才会退出线程池

特点:

  • 创建重用固定数量线程的线程池,不能随时新建线程
  • 当所有线程都处于活动状态时,如果提交了其他任务,他们将在队列中等待一个线程可用
  • 线程会一直存在,直到调用shutdown

3、周期定长线程池
newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行。
特点:

  • 设定延迟时间
  • 定期执行,空闲线程会进行保留

4、单线程化线程池
newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,
保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

特点:

  • 在任何情况下都不会有超过一个任务处于活动状态
  • 与newFixedThreadPool(1)不同是不能重新配置加入线程,使用FinalizableDelegatedExecutorService进行包装能保证执行顺序,先提交的先执行当线程执行中出现异常,去创建一个新的线程替换之

ExecutorService singleThreadPool = Executors.newFixedThreadPool(1);
ThreadPoolExecutor executor = (ThreadPoolExecutor) singleThreadPool;
executor.setCorePoolSize(4); 没问题,可以修改核心线程数
//但是如果Executors.newSingleThreadExecutor修改出错
*

  • 5、 周期单线程池
  • newSingleThreadScheduledExecutor创建一个单线程化的线程池,可以在指定延迟后指定线程任务。
  • 6、工作窃取算法的线程池
  • newWorkStealingPool(int)/()创建持有足够数量线程的线程池来支持给定的并行级别,该方法还会使用多个队列来减少竞争,无参则是根据CPU个数定义并行级别。
  • 工作窃取核心思想是,自己的活干完了去看看别人有没有没干完的活,如果有就拿过来帮他干。核心思想是work-stealing工作窃取,ForkJoinPool提供了一个更有效的利用线程的机制,当ThreadPoolExecutor还在用单个队列存放任务时,ForkJoinPool已经分配了与线程数相等的队列,当有任务加入线程池时,会被平均分配到对应的队列上,各线程进行正常工作,当有线程提前完成时,会从队列的末端窃取其他线程未执行完的任务,当任务量特别大时,CPU多的计算机会表现出更好的性能。
  • 实现机制是:为每个工作线程分配一个双端队列(本地队列)用于存放需要执行的任务,当自己的队列没有数据的时候从其它工作者队列中获得一个任务继续执行。

常见的五种线程池的适应场景

newCachedThreadPool
用来创建一个可以无限扩大的线程池,适用于服务器负载较轻,执行很多短期异步任务。

newFixedThreadPool
创建一个固定大小的线程池,因为采用无界的阻塞队列,所以实际线程数量永远不会变化,适用于可以预测线程数量的业务中,或者服务器负载较重,对当前线程数量进行限制。

newSingleThreadExecutor
创建一个单线程的线程池,适用于需要保证顺序执行各个任务,并且在任意时间点,不会有多个线程是活动的场景。

newScheduledThreadPool
可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。

newWorkStealingPool
创建一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行,适用于大耗时的操作,可以并行来执行

綫程池的7大參數 ThreadPoolExecutor

ThreadPoolExecutor参数含义

序号 名称 类型 含义
1 corePoolSize int 核心线程池大小
2 maximumPoolSize int 最大线程池大小
3 keepAliveTime long 线程最大空闲时间
4 unit TimeUnit 时间单位
5 workQueue BlockingQueue 线程等待队列
6 threadFactory ThreadFactory 线程创建工厂
7 handler RejectedExecutionHandler 拒绝策略

任务调度算法

  • 如果当前线程池线程个数小于corePoolSize则开启新线程,否则添加任务到阻塞队列
  • 如果任务队列满了,则尝试新开启线程执行任务,如果线程>maximumPoolSize则执行RejectedExecutionHandler。

工作队列

1、无界队列:队列大小无限制,常用的为无界的LinkedBlockingQueue,当任务耗时较长时可能会导致大量新任务在队列中堆积最终导致OOM。

2、有界队列:常用的有两类,一类是遵循FIFO原则的队列如ArrayBlockingQueue与有界的LinkedBlockingQueue,
另一类是优先级队列如PriorityBlockingQueue
> PriorityBlockingQueue中的优先级由任务的Comparator决定。
> 使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量。
> 实际开发中一般应该使用有界队列。

3、同步移交队列:如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue
作为等待队列
> SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入
SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列。

JAVA提供4种饱和策略

1、AbortPolicy直接抛出异常。
2、CallerRunsPolicy只用调用所在的线程运行任务。
3、DiscardOldestPolicy丢弃队列里最近的一个任务,并执行当前任务。
4、DiscardPolicy不处理,丢弃掉。

任务队列、核心线程数、最大线程数的逻辑关系

  • 当线程数小于核心线程数时,创建线程
  • 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列
  • 当线程数大于等于核心线程数,且任务队列已满
    • 若线程数小于最大线程数,创建线程
    • 若线程数等于最大线程数,调用拒绝执行处理程序(默认效果为:抛出异常,拒绝任务)

阿里开发规范为什么不允许Executors快速创建线程池

线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor方法,这样的处理方式让编程人员更加明确线程池的运行规则,规避资源耗尽的风险

说明: Executors返回的线程池对象的弊端:

  • FixedThreadPool和SingleThreadPool允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM
  • CachedThreadPool允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM

线程池相关的主要方法

java.util.concurrent.ExecutorService是java线程池框架的主要接口,用Future保存任务的运行状态
及计算结果,主要方法有:

  • void execute(Runnable)提交任务到线程池

  • Future submit(Runnable)提交任务到线程池并返回Future

  • Future submit(Runnable, T) 提交任务到线程池并返回Future, 第二个参数会作为计算结果封装到Future

  • Future submit(Callable)提交任务到线程池并返回Future,call方法的计算结果会封装到Future

  • List invokeAll(Collection extends Callable>) 批量提交任务并返回计算结果

  • T invokeAny(Collectionextends Callable> tasks) 只执行其中一个任务并返回结果

  • void shutdown() 线程池不再接受新任务,继续运行正在执行中的任务及等待中的任务

  • List shutdownNow() 线程池不再接受新任务,继续运行正在执行中的任务,返回待执行的任务列表

线程池的关闭

shutdownNow:对正在执行的任务全部发出interrupt(),停止执行,对还未开始执行的任务全部取消,并且返回还没开始的任务列表
shutdown:当调用shutdown后,线程池将不再接受新的任务,但也不会去强制终止已经提交或者正在执行中的任务

创建线程池的核心参数

  • corePoolSize与maximumPoolSize。corePoolSize核心线程池大小,线程池刚启动时来一个任务就会创建一个线程去执行这个任务,直到达到corePoolSize个线程,这时再来的任务就会加到任务队列中去,直到任务队列满了,这时会尝试创建新的线程,但是总线程数量必须少于maximumPoolSize,如果线程也不能创建了,这时就会拒绝执行任务了
  • workQueue 线程池所使用的任务阻塞队列,该阻塞队列的长度决定了能够缓冲任务的最大数量,queueCapacity任务队列容量
  • keepAliveTime表示线程没有任务执行时最多保持多久时间会终止
    参数的设置跟系统的负载有直接的关系,系统负载的相关参数:tasks每秒需要处理的最大任务数量;tasktime处理第个任务所需要的时间;responsetime系统允许任务最大的响应时间,比如每个任务的响应时间不得超过2秒。

如何合理配置线程池的大小

任务性质可分为:CPU密集型任务,IO密集型任务,混合型任务。

  • 对于计算密集型的任务,一个有N cpu个处理器的系统通常通过使用一个N cpu+1个线程的线程池来得最优的利用率
  • 对于IO密集型任务,包含了I/O和其他阻塞操作的任务,可以设置为2*N cpu
  • 最优的线程池的大小等于 【((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目 】
    • 比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32

可以先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来进行适当调整。
Runtime.getRuntime().availableProcessors():int获取CPU的核数

你可能感兴趣的:(Thread,java,jvm,开发语言)