java线程池ThreadPoolExecutor

一.线程池

1,这里记住最核心的类是ThreadPoolExecutor

2,在ExecuorService中提供了newSingleThreadExecutor,newFixedThreadPool,newCacheThreadPool,newScheduledThreadPool四个方法,这四个方法返回的类型是ThreadPoolExecutor。

3,这里Executor是接口,ExecutorService也是接口并继承了Executor.Executors是Executor的工具类,通过Executors.newSingleThreadExecutor可以调用上面四个方法。大家可以通过查看源码了解上图组成的各个关系。

这四种实现方式依次源码,可以看出底层都是实现的ThreadPoolExecutor类

 

  ThreadPoolExecutor核心类构造

 

4,线程池的核心参数说明

核心线程(corePool):有新任务提交时,首先检查核心线程数,如果核心线程都在工作,而且数量也已经达到最大核心线程数,则不会继续新建核心线程,而会将任务放入等待队列。

非核心线程/最大线程数(maximumPoolSize):当等待队列满了,如果当前线程数没有超过最大线程数,则会新建线程执行任务,那么核心线程和非核心线程到底有什么区别呢?说出来你可能不信,本质上它们没有什么区别,创建出来的线程也根本没有标识去区分它们是核心还是非核心的,线程池只会去判断已有的线程数(包括核心和非核心)去跟核心线程数和最大线程数比较,来决定下一步的策略。

线程活动保持时间 (keepAliveTime):线程空闲下来之后,保持线程的持续时间,超过这个时间还没有任务执行,该工作线程结束。

unit : keepAliveTime 参数的时间单位。

等待队列 (workQueue):等待队列用于存储当核心线程都在忙时,继续新增的任务,核心线程在执行完当前任务后,也会去等待队列拉取任务继续执行,这个队列一般是一个线程安全的阻塞队列,它的容量也可以由开发者根据业务来定制。

threadFactory : 执行程序创建新线程时使用的工厂。

拒接策略 (RejectedExecutionHandler):当等待队列已满,线程数也达到最大线程数时,线程池会根据拒接策略来执行后续操作,默认的策略是直接抛弃要加入的任务

5,4中这里要详细介绍的是workQueue,理解为任务队列

大家可以理解线程池中使用到了队列,队列也是线程池的组成部分之一。

任务队列分类:

五个队列所提供的各有不同:
  * ArrayBlockingQueue :一个由数组支持的先入先出有界队列。
  * LinkedBlockingQueue :一个由链接节点支持的先入先出可选有界队列。
  * PriorityBlockingQueue :一个由优先级堆支持的无界优先级队列。
  * DelayQueue :一个由优先级堆支持的、基于时间的调度队列。
  * SynchronousQueue :一个利用 BlockingQueue 接口的简单聚集(rendezvous)机制,无缓冲的等待队列,无界

SynchonousQueue: 同步队列,队列直接提交给线程执行而不保持它们,此时线程池通常是无界的
 
LinkedBlockingQueue: 无界对列,当线程池线程数达到最大数量时,新任务就会在队列中等待执行,
可能会造成队列无限膨胀(创建的时候this(Integer.MAX_VALUE);无限任务队列,导致oom,
当然它也提供了自定义队列数构造,可实现有界队列)
 
ArrayBlockingQueue : 有界队列,有助于防止资源耗尽,一旦达到上限,可能会造成新任务丢失



newSingleThreadExecutor、newFixedThreadPool使用的是LinkedBlockingQueue 
newCachedThreadPool 使用的是 SynchonousQueue 
newScheduledThreadPool使用的是 DelayedWorkQueue


newCachedThreadPool、newScheduledThreadPool中最大线程数(maximumPoolSize)
是Integer.MAX_VALUE(无限),导致线程会无限创建,导致oom

1. FixedThreadPool和SingleThreadExecutor使用无界队列LinkedBlockingQueue作为线程池的工作队列。CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,但 CachedThreadPool的maximumPool是无界的。这意味着,如果主线程提交任务的速度高于 maximumPool中线程处理任务的速度时,CachedThreadPool会不断创建新线程。当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。极端情况下, CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源。

2.FixedThreadPool使用无界队列LinkedBlockingQueue作为线程池的工作队列(队列的容量为 Integer.MAX_VALUE)。使用无界队列作为工作队列会对线程池带来如下影响。

    1)当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池中的线程数不会超过       corePoolSize。

    2)由于1,使用无界队列时maximumPoolSize将是一个无效参数。 

    3)由于1和2,使用无界队列时keepAliveTime将是一个无效参数。

    4)由于使用无界队列,运行中的FixedThreadPool(未执行方法shutdown()或 shutdownNow())不会拒绝任务   (不会调用RejectedExecutionHandler.rejectedExecution方法)。
 

阿里巴巴开发文档中禁止使用这四种方式创建,可自定义实现ThreadPoolExecutor类

6.四种拒绝策略

RejectedExecutionHandler rejected = null;
rejected = new ThreadPoolExecutor.AbortPolicy();
//默认,队列满了丢任务抛出异常
rejected = new ThreadPoolExecutor.DiscardPolicy();
//队列满了丢任务不抛异常
rejected = new ThreadPoolExecutor.DiscardOldestPolicy();
//将最早进入队列的任务删,之后再尝试加入队列
rejected = new ThreadPoolExecutor.CallerRunsPolicy();
//如果添加到线程池失败,那么主线程会自己去执行该任务;如果执行程序已关闭(主线程运行结束)
,则会丢弃该任务

当然也可以根据应用场景实现 RejectedExecutionHandler 接口,自定义饱和策略,如记录 日志或持久化存储不能处理的任务

二,队列

Queue: 基本上,一个队列就是一个先入先出(FIFO)的数据结构,注意是对尾插入,对头取出。

Queue接口与List、Set同一级别,都是继承了Collection接口。LinkedList实现了Deque接 口。

1,按阻塞队列和非阻塞队列划分为两类

1、没有实现的阻塞接口的LinkedList: 实现了java.util.Queue接口和java.util.AbstractQueue接口
  内置的不阻塞队列: PriorityQueue 和 ConcurrentLinkedQueue
  PriorityQueue 和 ConcurrentLinkedQueue 类在 Collection Framework 中加入两个具体集合实现。 
  PriorityQueue 类实质上维护了一个有序列表。加入到 Queue 中的元素根据它们的天然排序(通过其 java.util.Comparable 实现)或者根据传递给构造函数的 java.util.Comparator 实现来定位。
  ConcurrentLinkedQueue 是基于链接节点的、线程安全的队列。并发访问不需要同步。因为它在队列的尾部添加元素并从头部删除它们,所以只要不需要知道队列的大 小,          ConcurrentLinkedQueue 对公共集合的共享访问就可以工作得很好。收集关于队列大小的信息会很慢,需要遍历队列。

非阻塞对列常用的几个方法特性说明:

 add(E e):将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则会抛出异常;
 
  remove():移除队首元素,若移除成功,则返回true;如果移除失败(队列为空),则会抛出异常;
 
  offer(E e):将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则返回false;
 
  poll():移除并获取队首元素,若成功,则返回队首元素;否则返回null;
 
  peek():获取队首元素,若成功,则返回队首元素;否则返回null

2)实现阻塞接口的:
  java.util.concurrent 中加入了 BlockingQueue 接口和五个阻塞队列类。它实质上就是一种带有一点扭曲的 FIFO 数据结构。不是立即从队列中添加或者删除元素,线程执行操作阻塞,直到有空间或者元素可用。

这里怎么理解阻塞这里两个字呢?

使用非阻塞队列的时候有一个很大问题就是:它不会对当前线程产生阻塞,那么在面对类似消费者-生产者的模型时,就必须额外地实现同步策略以及线程间唤醒策略,简单理解就是非阻塞队列时,一个线程去拿队列里的东西,发现这个队列是空的,那紧接着这个线程就执行完了,可当有任务进来的时候还有重新启动一个线程去队列中拿(也就是唤醒策略),这个实现起来就非常麻烦。但是有了阻塞队列就不一样了,它会对当前线程产生阻塞,比如一个线程从一个空的阻塞队列中取元素,此时线程会被阻塞直到阻塞队列中有了元素。当队列中有元素后,被阻塞的线程会自动被唤醒(不需要我们编写代码去唤醒),也就是始终有个线程在等着这个队列,如果队列中有东西了,被阻塞的线程会主动去拿。这样提供了极大的方便性。理解阻塞两个字对下面线程池中理解核心线程数和最大线程数的关系很重要。

三.队列如何运行的,如何实现线程的重复利用

   

 

1. 首先会先判断线程池的状态,即是否在运行状态,若线程为非运行状态 ,则会拒绝;
接下来会去判断线程数是否小于核心线程数,若小于核心线程数,则会新建工作线程并执行任务,随着任务的增多,线程数会慢慢增加至核心线程数;
如果此时还有任务提交,就会判断阻塞队列workQueue是否已满,若没满,则会将任务放入阻塞队列中,等待工作工作线程获得并执行,如果任务提交非常多,会使得阻塞队列达到上限,会去判断线程数是否小于最大线程数maximumPoolSize, 若小于最大线程数,线程池会添加工作线程并执行任务;
如果仍然有大量任务提交,使得线程数等于最大线程数,如果此时还有任务提交,那么就会被拒绝。

2.execute和submit的区别是什么?

a. execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。(从源码可知execute()方法输入的任务是一个Runnable类的实例)。
b.submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务还没有执行完成。
 

   

 3.线程的重复利用:

一个线程一般在执行完任务之后就结束了,怎么再让它执行下一个任务呢?

线程重用的核心是:Thread.start()只能调用一次,一旦这个调用结束,则该线程就到了stop状态,不能再次调用start方法

因此,要想达到复用的目的,则必须从Runnable接口的run()方法上入手,可以这样设计这个Runnable.run()方法(外面的run()方法):

它本质上是个无限循环,跑的过程中不断检查我们是否有新加入的子Runnable对象(内部的runnable:run(),用来实现我们自己的任务),有就调用我们的run(),其实就一个大run()把其它小run()#1,run()#2,…个串联起来。不停地处理提交的Runnable任务。
 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值