JAVA线程池

目录

池的思想

JDK中线程池的使用

线程池的核心父接口ExecutorService 

ThreadPoolExecutor子类的核心构造方法参数

ScheduledExecutorService

Execuctors 


池的思想

虽然创建和销毁线程比较小(和进程相比),但是当系统中线程数量比较多,这个开销就比较可观的,所以引入线程池

  • 目的就是让某些对象被多次重复利用,减少频繁创建和销毁对象带来的开销问题(这些对象一定可以复用的)
  • 类比数据库池,创建和销毁数据库的连接就是一个比较耗时的操作,表示当前用户不再使用此连接,就回收到连接池中(同一个连接可以被多个用户使用多次,减少了每次创建和销毁连接的系统开销)
  • 同样的,不同的线程也就是run方法的内容不同,线程的大致流程都是一样的,因此为了避免重复创建和销毁线程带来的开销,可以让线程"复用"起来(x线程池最大的好处就是每次减少启动和销毁线程的损耗,提供=高时间和空间的利用率)
  • 线程池内部创建好了若干个线程,这些线程都是runnable,只需要从系统中取出任务,就可以立即开始执行

⽤线程池主要有以下三个原因:

  • 1. 创建/销毁线程需要消耗系统资源,线程池可以复⽤已创建的线程。
  • 2. 控制并发的数量。并发数量过多,可能会导致资源消耗过多,从⽽造成服务器崩溃。(主要原因)
  • 3. 可以对线程做统⼀管理。
     

Java 线程数过多会造成什么异常
线程的生命周期开销非常高
消耗过多的 CPU
资源如果可运行的线程数量多于可用处理器的数量,那么有线程将会被闲置。大量空闲的线程会占用许多内存,给垃圾回收器带来压力,而且大量的线程在竞争 CPU资源时还将产生其他性能的开销。
降低稳定性JVM
在可创建线程的数量上存在一个限制,这个限制值将随着平台的不同而不同,并且承受着多个因素制约,包括 JVM 的启动参数、Thread 构造函数中请求栈的大小,以及底层操作系统对线程的限制等。如果破坏了这些限制,那么可能抛出OutOfMemoryError 异常。
 

JDK中线程池的使用

 先理清常用的线程池的类和接口的之间的关系

线程池的核心父接口ExecutorService 

submit

  •  提交一个任务到线程池,线程池就会派遣空闲的线程执行任务

shutdownNow

  •  立即尝试终止线程池中所有的线程(是否空闲)

  •  停止所有处在空闲状态的线程,正在执行任务的线程,等待任务执行结束再停止,其两方法都是终止所有的线程然后销毁线程池

ThreadPoolExecutor子类的核心构造方法参数

  

  •  CallerRunsPolicy 返回给线程的调用者处理
  • AbortPolicy 超出负荷的任务直接拒绝,抛出异常
  • DiscardOldestPolicy:丢弃队列中最老的任务(排队直接最长的任务)
  • DisCardPolicy:丢弃新来的任务

线程池的工作流程

BlockingQueue workQueue:阻塞队列,维护着等待执⾏的Runnable任务对象

  • 1. LinkedBlockingQueue

链式阻塞队列,底层数据结构是链表,默认⼤⼩是 Integer.MAX_VALUE ,
也可以指定⼤⼩。

  • 2. ArrayBlockingQueue

数组阻塞队列,底层数据结构是数组,需要指定队列的⼤⼩。

  • 3. SynchronousQueue

同步队列,内部容量为0,每个put操作必须等待⼀个take操作,反之亦
然。

  • 4. DelayQueue

延迟队列,该队列中的元素只有当其指定的延迟时间到了,才能够从队列
中获取到该元素
 

ScheduledExecutorService

Execuctors 

使用这个类可以创建JDK内置的四大线程池(Java中带s的类都是工具类,Arrays)

固定大小线程池 

动态变化的线程池 

单线程的线程池 

  • 单线程有意义吗,和我们创建一个线程的区别在哪,我们创建一个线程只能执行一个任务,一个任务执行完就销毁了,但是单线程池,虽然同一个时间只能执行一个任务,当这个任务执行结束之后,继续在工作队列调度一个新的任务继续执行(还是减少了销毁和创建线程的开销)  

定期线程池

阿里编码归约:尽量不要使用内置线程池,最好根据实际的业务需求,定制线程池自己new ThreadPoolExector对象,传递相关参数

Executors框架实现的就是线程池的功能

  • Executors工厂类中提供的newCachedThreadPool、newFixedThreadPool newScheduledThreadPool 、newSingleThreadExecutor 等方法其实也只是ThreadPoolExecutor的构造函数参数不同而已。通过传入不同的参数,就可以构造出适用于不同应用场景下的线程池,

newCachedThreadPool

  • 特点:newCachedThreadPool创建一个可缓存线程池,如果当前线程池的长度超过了处理的需要时,它可以灵活的回收空闲的线程,当需要增加时, 它可以灵活的添加新的线程,而不会对池的长度作任何限制
  • 缺点:他虽然可以无限的新建线程,但是容易造成堆外内存溢出,因为它的最大值是在初始化的时候设置为 Integer.MAX_VALUE,一般来说机器都没那么大内存给它不断使用。当然知道可能出问题的点,就可以去重写一个方法限制一下这个最大值

总结:线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

newFixedThreadPool

  • 特点:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。定长线程池的大小最好根据系统资源进行设置。
  • 缺点:线程数量是固定的,但是阻塞队列是无界队列。如果有很多请求积压,阻塞队列越来越长,容易导致OOM(超出内存空间)

总结:请求的挤压一定要和分配的线程池大小匹配,定线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()
 

newSingleThreadExecutor
特点:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它,他必须保证前一项任务执行完毕才能执行后一项。
保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
缺点:缺点的话,很明显,他是单线程的,高并发业务下有点无力
总结:保证所有任务按照指定顺序执行的,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它
 

在 Java 中 Executor 和 Executors 的区别

  • Executors 工具类的不同方法按照我们的需求创建了不同的线程池,来满足业务的需求。Executor 接口对象能执行我们的线程任务。
  • ExecutorService 接口继承了 Executor 接口并进行了扩展,提供了更多的方法我们能获得任务执行的状态并且可以获取任务的返回值。使用 ThreadPoolExecutor 可以创建自定义线程池
     

线程池中 submit() 和 execute() 方法有什么区别
相同点:

  • 相同点就是都可以开启线程执行池中的任务。

不同点:

  • 接收参数:execute()只能执行 Runnable 类型的任务。submit()可以执行 Runnable 和
  • Callable 类型的任务。
  • 返回值:submit()方法可以返回持有计算结果的 Future 对象,而execute()没有
  • 异常处理:submit()方便Exception处理
     

ThreadPoolExecutor饱和策略有哪些
如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任时,

  • ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException来拒绝新任务的处理。
  • ThreadPoolExecutor.CallerRunsPolicy:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。
  • ThreadPoolExecutor.DiscardPolicy:不处理新任务,直接丢弃掉。
  • ThreadPoolExecutor.DiscardOldestPolicy: 此策略将丢弃最早的未处理的任务请求
     

如何合理分配线程池大小
要合理的分配线程池的大小要根据实际情况来定,简单的来说的话就是根据CPU密集和IO密集来分配
什么是CPU密集
CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。
CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),而在单核CPU上,无论你开几个模拟的多线程,该任务都不可能得到加速,因为CPU总的运算能力就那样。
什么是IO密集

IO密集型,即该任务需要大量的IO,即大量的阻塞。在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。所以在IO密集型任务中使用多线程可以大大的加速程序运行,即时在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。
分配CPU和IO密集:

  • 1. CPU密集型时,任务可以少配置线程数,大概和机器的cpu核数相当,这样可以使得每个线程都在执行任务
  • 2. IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数

线程等待时间比CPU执行时间比例越高,需要越多线程。
线程CPU执行时间比等待时间比例越高,需要越少线程。
 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

库里不会投三分

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值