线程池

为什么要用线程池?

单线程方式存在以下几个问题:

  • 线程的工作周期:假设线程创建所需时间为T1,线程执行任务所需时间为T2,线程销毁所需的时间为T3,往往是T1+T3大于T2,所以如果频繁的创建线程会损耗过多的额外时间。
  • 如果有任务来了,再去创建线程的话效率比较低,如果从一个池子中可以直接获取可用的线程,那么效率会有所提升。所以线程池省去了任务过来要先创建线程的过程,节省了时间,提升了效率。
  • 线程池可以管理和控制线程,因为线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
  • 线程池提供队列,存放缓冲等待执行的任务。

通过线程池创建线程从调用 API 角度来说分为两种,一种是原生的线程池,另外该一种是通过 Java 提供的并发包来创建,后者其实是对原生的线程池创建方式做了一次简化包装,让调用者使用起来更方便,但道理都是一样的。

ThreadPoolExecutor

通过ThreadPoolExecutor创建线程池,API如下所示:

  • corePoolSize:指定核心线程数(核心池大小)。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,预创建线程,即在任务到来之前就创建corePoolSize个或者一个线程。默认情况下,在创建了线程池后,线程池中的线程池数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数达到corePoolSize后,就会把到达的任务放到缓存队列中。
  • maximumPoolSize线程池最大线程数,它表示在线程池中最多能创建多少个线程。
  • keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于 corePoolSize 时,keepAliveTime 才会起作用,直到线程池中的线程数不大于 corePoolSize,即当线程池中的线程数大于 corePoolSize 时,如果一个线程空闲的时间达到 keepAliveTime,则会终止,直到线程池中的线程数不超过 corePoolSize。但是如果调用了 allowCoreThreadTimeOut(boolean) 方法,在线程池中的线程数不大于 corePoolSize 时,keepAliveTime 参数也会起作用,直到线程池中的线程数为0。
  • unit:参数keepAliveTime的时间单位
  • workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue。
  • threadFactory线程工厂,用来创建线程。
  • handler:表示当拒绝处理服务时的策略(拒绝策略),有以下四种取值: ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)。 ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。

线程池之间的参数协作如图所示(注意数字顺序):

1、任务优先向CorePool中提交,创建核心线程执行任务 2、在CorePool满了之后,任务被提交提交到任务队列,等待线程池空闲 3、在任务队列满了之后,但CorePool中还没有空闲线程,那么任务将被提交到maxPool中,创建非核心线程执行任务 4、msxPool满了之后执行task拒绝策略 具体流程图如下:

Executors

  Executor框架是一个根据一组执行策略调用、调度、执行和控制的异步任务的框架。   无限制的创建线程会引起应用程序内存溢出。所以创建一个线程池是个更好的的解决方案,因为可以限制线程的数量并且可以回收再利用这些线程。   利用Executors框架可以非常方便的创建一个线程池,Java通过Executors提供四种线程池,分别为:

  • newSingleThreadExecutor:创建一个线程的线程池,在这个线程池中始终只有一个线程存在。如果线程池中的线程因为异常问题退出,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
  • newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
  • newCachedThreadPool:可根据实际情况,调整线程数量的线程池,线程池中的线程数量不确定,如果有空闲线程会优先选择空闲线程,如果没有空闲线程并且此时有任务提交会创建新的线程。在正常开发中并不推荐这个线程池,因为在极端情况下,会因为 newCachedThreadPool 创建过多线程而耗尽 CPU 和内存资源。
  • newScheduledThreadPool:此线程池可以指定固定数量的线程来周期性的去执行。比如通过 scheduleAtFixedRate 或者 scheduleWithFixedDelay 来指定周期时间。

推荐使用ThreadPoolExecutor方式。   阿里的 Java 开发手册里有一条是不推荐使用 Executors 去创建线程池,而是推荐去使用 ThreadPoolExecutor 来创建。   这样做的主要原因是:使用 Executors 创建线程池不会传入核心参数,而是采用的默认值,这样的话我们往往会忽略掉里面参数的含义,如果业务场景要求比较苛刻的话,存在资源耗尽的风险;另外采用 ThreadPoolExecutor 的方式可以让我们更加清楚地了解线程池的运行规则,不管是面试还是对技术成长都有莫大的好处。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 线程基本概念

    一般来说创建线程有三种方式: 方式一:继承java.lang.Thread类,覆写run()方法 方式二:实现java.lang.Runnable接口,实现ru...

    Java阿呆
  • BIO与反应器模式

      我们熟知的Socket编程就是一种BIO,一个socket连接一个处理线程(这个线程负责这个Socket连接的一系列数据传输操作)。阻塞的原因在于:操作系统...

    Java阿呆
  • 谈谈volatile

    volatile通常被比喻成“轻量级的synchronized”,也是Java并发编程中比较重要的一个关键字。和synchronized不同,volatile是...

    Java阿呆
  • Java多线程知识小抄集(二)

    27. ConcurrentHashMap ConcurrentHashMap是线程安全的HashMap,内部采用分段锁来实现,默认初始容量为16,装载因子为0...

    用户1263954
  • 3.并发编程多线程(理论部分)

      线程顾名思义,就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程

    changxin7
  • (2021最新版)Java后端面试题|Java多线程与并发

    面试前还是很有必要针对性的刷一些题,很多朋友的实战能力很强,但是理论比较薄弱,面试前不做准备是很吃亏的。这里整理了很多面试常考的一些面试题,希望能帮助到你面试前...

    程序员追风
  • 【JavaP6大纲】多线程篇:线程的生命周期,什么时候会出现孤儿进程,僵尸进程?它们之间的危害是什么?如何处理僵尸进程?

    新建(new Thread):当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。例如:Thread t1=new Thread(); ...

    java_wxid
  • 创建线程池有哪几种方式?

    Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorS...

    美食江湖
  • Java多线程和线程池

    在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,服务器在创建和销毁线程上花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际...

    全栈程序员站长
  • ​线程、进程对比

    操作系统调度切换多个线程要比切换调度进程在速度上快的多。而且进程间 内存无法共享,通讯也比较麻烦。

    忆想不到的晖

扫码关注云+社区

领取腾讯云代金券