栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

线程池总结(一)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

线程池总结(一)

         Java线程Thread类,所有的线程对象都必须是Thread类或其子类的实例,Java可以用三种方式来创建线程:

  • 继承Thread类创建线程
  • 实现Runnable接口创建线程
  • 使用Callable和Future创建线程

        做一些简单的操作开启一个线程处理数据可通过上面方式来完成,但是频换创建销毁线程的话还是要用线程池更节省资源,降低系统开销而且还能提供定时执行,定期执行,单线程,并发控制等功能。

        线程池的本质是对任务和线程的管理,做到了将任务和线程两者解耦。线程池对任务的管理可看作生产者消费者的关系,通过阻塞队列的存与取。阻塞队列缓存待执行的任务,工作线程从阻塞队列中获取任务。线程池对线程的管理,是结合线程池状态,已有线程的状态,核心线程数和最大线程数、阻塞队列状态做出增加、执行任务、回收、复用等操作,体现了享元模式和池化思想。

         想必很多人都看过阿里开发手册,里面提到一条:线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式。Executors可创建三类线程池:

  • 创建返回ThreadPoolExecutor对象
  • 创建返回ScheduleThreadPoolExecutor对象
  • 创建返回ForkJoinPool对象

本篇只讨论常用的ThreadPoolExecutor对象。

Executors创建返回ThreadPoolExecutor对象:
  • Executors#newCachedThreadPool => 创建可缓存的线程池
  • Executors#newSingleThreadExecutor => 创建单线程的线程池
  • Executors#newFixedThreadPool => 创建固定长度的线程池

ps: ScheduledThreadPoolExecuto是ThreadPoolExecutor的子类,创建过程也是调用的父类的构造方法

不建议Executors创建线程池的原因:
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue());
}

     提交任务时,corePoolSize因为0不创建核心线程,SynchronousQueue是一个不存储元素的队列,可以理解为队列永远是满的,因此最终会创建非核心线程来执行任务。对于非核心线程空闲60s时将被回收。因为Integer.MAX_VALUE非常大,可以认为是无限创建线程,在服务器资源有限的情况下容易引起OOM异常。

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new linkedBlockingQueue()));
    }

     任务提交时,首先会创建一个核心线程来执行任务,如果超过核心线程的数量,将会放入队列中,因为linkedBlockingQueue是长度为Integer.MAX_VALUE的队列,可以认为是无界队列,因此往队列中可以插入无限多的任务,在资源有限的时候容易引起OOM异常,同时因为无界队列,maximumPoolSize和keepAliveTime参数将无效,压根就不会创建非核心线程

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new linkedBlockingQueue());
}

 和SingleThreadExecutor类似,由于使用的是linkedBlockingQueue,在资源有限的时候容易引起OOM异常。该线程池只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。现行大多数GUI程序都是单线程的。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
}

      创建一个定长线程池,支持定时及周期性任务执行。ScheduledThreadPoolExecutor的设计思想是,每一个被调度的任务都会由线程池中一个线程去执行,因此任务是并发执行的,相互之间不会受到干扰。需要注意的是,只有当任务的执行时间到来时,ScheduedExecutor 才会真正启动一个线程,其余时间 ScheduledExecutor 都是在轮询任务的状态。DelayedWorkQueue长度16,最大线程数因为是Integer.MAX_VALUE非常大,可以认为是无限创建线程,在服务器资源有限的情况下容易引起OOM异常。

总结下各类线程池的配置,以及其阻塞队列类型,使用场景。

类型核心线程数最大线程数阻塞队列说明/使用场景
FixedThreadPool构造时传入与核心线程数相同linkedBlockingQueue线程数量固定,只有核心线程并且不会被回收,没有超时机制
CachedThreadPool0Integer.MAX_VALUESynchronousQueue线程数量不固定的线程池,只有非核心的线程,当线程都处于活动状态时,直接创建新线程来处理新任务,否则就利用空闲的线程。处于空闲状态超过60s的线程被回收
ScheduledThreadPool构造时传入Integer.MAX_VALUEDelayedWorkQueue非核心线程在闲置时立刻回收,主要用于执行定时任务和固定周期的重复任务
SingleThreadExecutor11linkedBlockingQueue只有一个核心线程,确保所有任务在同一线程中按顺序执行

  自定义线程池ThreadPoolExecutor对象

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.tonanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
}

 整个任务工作流程如下:

线程池的拒绝策略

所有拒绝策略都实现了接口 RejectedExecutionHandler,类型如下:

AbortPolicy:默认拒绝策略,中断调用者的处理过程,直接抛出拒绝异常RejectedExecutionException

CallerRunsPolicy:只会用调用者所在线程来运行任务,也就是说任务不会进入线程池。如果线程池已经被关闭,则直接丢弃该任务。

DiscardOledestPolicy:丢弃队列中最老的,然后再次尝试提交新任务。

DiscardPolicy:默默丢弃无法加载的任务。

通过实现 RejectedExecutionHandler 自定义

核心线程数如何设置?

在数设置之前需要说下两个概念,CPU密集型(计算密集型),IO密集型。

CPU密集型:是指系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是I/O在很短的时间就可以完成,而CPU还有许多运算要处理,CPU Loading很高。对于CPU密集型计算,多线程本质上是提升多核cpu的利用率,比如8核cpu,每核一个线程,理论上8个线程就可以了,如果设置过多的线程,实际上并不会起到很好的效果,反而由于线程数都想去利用cpu资源导致不必要的上下文切换,导致性能下降。

CPU密集任务:比如像加解密,压缩、计算等一系列需要大量耗费 CPU 资源的任务,大部分场景下都是纯 CPU 计算。

IO密集型:是指系统的CPU性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。

IO 密集型任务:比如像 MySQL 数据库、文件的读写、网络通信等任务,这类任务不会特别消耗 CPU 资源,但是 IO 操作比较耗时,会占用比较多时间。

核心线程数

CPU密集型:核心线程数=CPU核心数(或 核心线程数=CPU核心数+1)

I/O密集型:核心线程数=2*CPU核心数(或 核心线程数=CPU核心数/(1-阻塞系数))

混合型:核心线程数=(线程等待时间/线程CPU时间+1)*CPU核心数

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/274620.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号