Java线程池

Java的线程池,各个参数的作用,如何进行的?

1.线程池核心参数

public ThreadPoolExecutor(
  int corePoolSize,
int maximumPoolSize,
long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

线程池可以通过ThreadPoolExecutor来创建,我们来看一下它的几个核心参数:

  • corePoolSize: 线程池核心线程数最大值
  • maximumPoolSize: 线程池最大线程数大小
  • keepAliveTime: 线程池中非核心线程空闲的存活时间大小
  • unit: 线程空闲存活时间单位
  • workQueue: 存放任务的阻塞队列
  • threadFactory: 用于设置创建线程的工厂,可以给创建的线程设置有意义的名字,可方便排查问题。
  • handler: 线城池的饱和策略事件,主要有四种类型。

核心线程和非核心线程区别:

核心线程: 可闲置 不会被销毁  。(ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true时,keepAliveTime同样会作用于核心线程)

非核心线程:非核心线程当闲置时间超过 keepAliveTime这个时长,非核心线程就会被回收。

核心线程数就像是工厂正式工,最大线程数,就是工厂临时工作量加大,请了一批临时工,临时工加正式工的和就是最大线程数,等这批任务结束后,临时工要辞退的,而正式工留下。

2.线程池创建流程

  • 提交一个任务,如果当前工作中的线程数量少于corePoolSize,就创建新的线程来执行任务。
  • 当线程池的工作中的线程数量达到了corePoolSize,新提交的任务,会被放进任务队列workQueue排队等待执行。
  • 当线程池里面存活的线程数达到corePoolSize了,并且任务队列workQueue也满,判断线程数是否达到maximumPoolSize,即最大线程数是否已满,如果没到达,创建一个非核心线程执行提交的任务。
  • 如果当前的线程数达到了maximumPoolSize,还有新的任务过来的话,直接采用拒绝策略处理。

  

3.四种拒绝策略

  • AbortPolicy(抛出一个异常,默认的)
  • DiscardPolicy(直接丢弃任务)
  • DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池)
  • CallerRunsPolicy(交给线程池调用所在的线程进行处理)

4. 线程池的作用:

池化技术应用:线程池、数据库连接池、http连接池等等。

池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。

使用线程池的好处:

  • 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

  • 提高响应速度:当任务到达时,可以不需要等待线程创建就能立即执行。

  • 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,监控和调优。

5.线程池中线程的生命周期

设:我们有一个coreSize=10,maxSize=20,keepAliveTime=60s,queue=40
1、池初始化时里面没有任何线程。
2、当有一个任务提交到池就创建第一个线程。
3、若继续提交任务,有空闲线程就调拨空闲线程来处理任务?若没有线程空闲则再新建一个线程来处理,如此直到coreSize。【预热阶段
4、若继续提交任务,有空闲线程就调拨空闲线程来处理任务,如果没有空闲线程(10个)则将任务缓存到queue中排队等待。
5、若继续提交任务,而已有线程不空闲,且queue也满了,则新建线程,并将最新的任务优先提交给新线程处理。
6、若继续提交任务,且所有线程(20个)仍不空闲,queue也是满的,此时就会触发池的拒绝机制。
8、一旦有任何线程空闲下来就会从queue中消费任务,直到queue中任务被消费完。
9、当总忙碌线程个数不超过coreSize时,闲暇线程休息keepAliveTime过后会被销毁。
10、而池中一直保留coreSize个线程存活。

6.线程池的实现原理

其实java线程池的实现原理很简单,说白了就是一个线程集合workerSet和一个阻塞队列workQueue。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队列中有任务了就取出来继续执行

线程池都有哪几种工作队列?

1.线程池的5种工作队列

  • ArrayBlockingQueue
  • LinkedBlockingQueue
  • DelayQueue
  • PriorityBlockingQueue
  • SynchronousQueue

任务太多的时候,工作队列用于暂时缓存待处理的任务,jdk中常见的5种阻塞队列:

ArrayBlockingQueue(数组阻塞队列):是一个基于数组结构的有界阻塞队列,此队列按照先进先出原则对元素进行排序

LinkedBlockingQueue(链表阻塞队列):是一个基于链表结构的阻塞队列,此队列按照先进先出排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool使用了这个队列。

DelayQueue(延迟队列):是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列。

PriorityBlockingQueue(优先级阻塞队列):优先级队列,进入队列的元素按照优先级会进行排序。

SynchronousQueue (同步队列):一个不存储元素的阻塞队列,每个插入操作必须等到另外一个线程调用移除操作,否则插入操作一直处理阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用这个队列。

 

几种常见的线程池及使用场景

1. 四种常用的线程池

  • newFixedThreadPool (固定数目线程的线程池)
  • newCachedThreadPool(可缓存线程的线程池)
  • newSingleThreadExecutor(单线程的线程池)
  • newScheduledThreadPool(定时及周期执行的线程池)

a.newFixedThreadPool:

  • 通俗:创建可容纳固定数量线程的池子,每个线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)
  • 适用:FixedThreadPool 适用于处理CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的少的分配线程,即适用执行长期的任务。
  • 特点:
    • 核心线程数和最大线程数大小一样
    • 没有所谓的非核心线程空闲时间,即keepAliveTime为0
    • 阻塞队列为无界队列LinkedBlockingQueue

  问:使用无界队列的线程池会导致内存飙升吗?

  答:会的,newFixedThreadPool使用了无界的阻塞队列LinkedBlockingQueue,如果线程获取一个任务后,任务的执行时间比较长(比如,上面demo设置了10秒),会导致队列的任务越积越多,导致机器内存使用不停飙升, 最终导致OOM。

b.newCachedThreadPool:

  • 通俗:当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可以线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。
  • 适用:执行很多短期异步的小程序或者负载较轻的服务器.(用于并发执行大量短期的小任务。)
  • 特点:
    • 核心线程数为0
    • 最大线程数为Integer.MAX_VALUE
    • 阻塞队列是SynchronousQueue
    • 非核心线程空闲存活时间为60秒

c.newSingleThreadExecutor:

  • 通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
  • 适用:适用于串行执行任务的场景,一个任务一个任务地执行。
  • 特点:
    • 核心线程数为1
    • 最大线程数也为1
    • 阻塞队列是LinkedBlockingQueue
    • keepAliveTime为0

d.newScheduledThreadPool:

  • 通俗:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构
  • 适用:周期性执行任务的场景,需要限制线程数量的场景
  • 特点:
      • 最大线程数为Integer.MAX_VALUE
      • 阻塞队列是DelayedWorkQueue
      • keepAliveTime为0
      • scheduleAtFixedRate() :按某种速率周期执行
      • scheduleWithFixedDelay():在某个延迟后执行

2.线程池状态:

线程池有这几个状态:RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED。

RUNNING

  • 该状态的线程池会接收新任务,并处理阻塞队列中的任务;
  • 调用线程池的shutdown()方法,可以切换到SHUTDOWN状态;
  • 调用线程池的shutdownNow()方法,可以切换到STOP状态;

SHUTDOWN

  • 该状态的线程池不会接收新任务,但会处理阻塞队列中的任务;
  • 队列为空,并且线程池中执行的任务也为空,进入TIDYING状态;

STOP

  • 该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;
  • 线程池中执行的任务为空,进入TIDYING状态;

TIDYING

  • 该状态表明所有的任务已经运行终止,记录的任务数量为0。
  • terminated()执行完毕,进入TERMINATED状态

TERMINATED

  • 该状态表示线程池彻底终止

ThreadPoolExecutor的运行状态有5种,分别为:

 

其生命周期转换如下入所示:

 

 

 3.线程池ThreadPoolExecutor中定义的生命周期中的状态及相关方法:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3; // =29
private static final int CAPACITY   = (1 << COUNT_BITS) - 1; // =000 11111...

// runState is stored in the high-order bits
private static final int RUNNING    = -1 << COUNT_BITS; // 111 00000...
private static final int SHUTDOWN   =  0 << COUNT_BITS; // 000 00000...
private static final int STOP       =  1 << COUNT_BITS; // 001 00000...
private static final int TIDYING    =  2 << COUNT_BITS; // 010 00000...
private static final int TERMINATED =  3 << COUNT_BITS; // 011 00000...

// 线程池的状态
private static int runStateOf(int c)     { return c & ~CAPACITY; }
// 线程池中工作线程的数量
private static int workerCountOf(int c)  { return c & CAPACITY; }
// 计算ctl的值,等于运行状态“加上”线程数量
private static int ctlOf(int rs, int wc) { return rs | wc; }

 

posted @ 2020-03-13 15:37  藤原拓海7  阅读(338)  评论(0编辑  收藏  举报