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; }