- 1.Java线程池类图:
- 2.线程池状态
- ThreadPoolExecutor使用int的高3位来表示线程池状态,低29位来表示线程数量
| 状态名 | 高3位 | 接收新任务 | 处理阻塞任务队列 | 说明 |
|---|---|---|---|---|
| RUNNING | 111 | Y | Y | |
| SHUTDOWN | 000 | N | Y | 不会接收新任务,但会处理阻塞队列剩余任务 |
| STOP | 001 | N | N | |
| TIDYING | 010 | - | - | |
| TERMINATED | 011 | - | - |
- SHUTDOWN:是指调用了线程池的shutdown()方法,此时意图停止线程池,它是一种比较温和的,正在执行的任务和阻塞队列中的任务,都不会取消掉,等这些任务都被处理掉后,线程池才会关闭,但是调用了shutdown()以后,就不会再接收新任务了
- STOP:是指调用了线程池中的shutdownNow()方法,暴力停止线程池,不会接收新任务,正在执行的任务也会被打断(通过调用interrupt()来打断),阻塞队列的任务也会被抛弃掉
- TIDYING:是一个过渡状态,当任务全部执行完毕,活动的线程也为0了,线程池就处于TIDYING,线程池即将进入TERMINATED状态
- TERMINATED:线程池已经不能工作了,处于中介状态
从数字上比较:TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING(RUNNING为负数,因为是高三位,最高位为符号位)
为了保证状态信息 以及 线程数量在赋值时的原子性;他们被存储在一个原子变量ctl中,目的是将线程池状态与线程个数合二为一,这样就可以用一次cas原子操作进行赋值
// c为旧值,ctlOf返回结果为新值
ctl.compareAndSet(c, ctlOf(targetState, workCountOf(c)));
// rs为高3位,代表线程池状态,wc为低29位代表线程个数,ctlOf是合并他们
private static int ctlOf(int rs, int wc){return rs | wc;}
- 3.Java线程池构造方法
public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueueworkQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
- corePoolSize:核心线程数(最多保留的线程数)
- maximumPoolSize:最大线程数目
- keepAliveTime:生存时间-针对救急线程
- unit:时间单位-针对救急线程生存时间
- workQueue:阻塞队列
- threadFactory:线程工厂,可以为线程创建时起名字
- handler:拒绝策略
线程池中的线程是采用懒加载机制的,当需要用到线程时才创建,并不是一开始就创建好所有的线程
- 1.创建线程池,并设置相应的参数
- 2.当来了一个任务,线程池中会创建一个核心线程来执行任务
- 3.后边又来了多个任务,当所有的核心线程都在执行任务,即核心线程数达到corePoolSize时,新来的任务会被放入workQueue,即阻塞队列
- 4.当阻塞队列被放满后,又来了新的任务,那么就会开始创建救急线程,执行新来的任务(注意救急线程处理的是阻塞队列放不下的任务)
- 5.救急线程执行完任务后,他会有一个生存时间,如果是到时间了,依旧没有任务给救急线程执行,那么救急线程就会被销毁掉
- 6.如果救急线程也达到最大创建数目,即核心线程数 + 救急线程数 = maximumPoolSize时,新来的任务就会执行拒绝策略
注意:核心线程被创建后,即使后期没有任务,它也会被保留在线程池中,不会被回收,会一直运行
如果阻塞队列选择了有界队列,那么任务数目超过了阻塞队列大小时,会创建救急线程;如果选择的是无界队列,那么就不存在救急线程的概念;即救急线程是配合有界阻塞队列来使用
JDK提供了4中拒绝策略的实现:(abort: 中断、终止 reject:拒绝 discard:抛弃、丢弃)
-
1.AbortPolicy:让调用者抛出RejectedExecutionException异常,这是默认策略
-
2.CallerRunsPolicy:让调用者运行任务
-
3.DiscardPolicy:放弃本次任务
-
4.DiscardOldestPolicy:放弃队列中最早的任务,本任务取而代之
-
RPC框架Dubbo的实现:在抛出RejectedExecutionException异常之前会记录日志,并dump线程栈信息,方便定位
-
通信框架 Netty的实现,是创建一个新线程来执行任务
-
ActiveMQ的实现:加一个超时等待(60s),时间内会不断尝试放入队列
-
PinPoint的实现,它使用了一个拒绝策略量,会逐一尝试策略链中的每一种拒绝策略
-
但是ThreadPoolExecutor类并不容易掌握,JDK就提供了一个Executors类,该类中提供了众多工厂方法来创建各种用途的线程池
-
4.Executors
- 4.1.newFixedThreadPool:创建固定大小的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new linkedBlockingQueue());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new linkedBlockingQueue(),
threadFactory);
特点:没有救急线程,因此也无需超时时间
阻塞队列是无界的,可以放任意数量的任务
适用于任务量已知,不是特别繁忙,不会一次性来大量的任务,相对耗时的任务
- 4.2.NewCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue(),
threadFactory);
}
特点:
-
核心线程数为0,即全都是救急线程,maximumPoolSize = Integer.MAX_VALUE; 救急线程的空闲生存时间是60s,救急线程可以无限创建
-
队列采用了SynchronousQueue实现,它没有容量,没有线程来取是放不进去的(一手交钱、一手交货)
-
整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲1分钟后释放线程
-
适用于任务数比较密集,但每个任务执行时间比较短的情况
-
4.3.newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new linkedBlockingQueue()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new linkedBlockingQueue(),
threadFactory));
}
-
使用场景:希望多个任务排队执行,线程数固定为1,任务数多于1时,会放入无界队列排队。任务执行完毕,这个唯一的线程也不会释放
-
区别:如果是自己创建一个单线程串行执行任务,如果任务执行失败而终止,那么没有任何补救措施,而线程池中即使之前的线程由于意外结束了,还会新建一个线程,保证线程池的正常工作
-
Executor.newSingleThreadExecution()线程数为1,不能修改
- FinalizableDelegatedExecutorService应用的是装饰器模式,只对外暴露了ExecutorService接口,因此不能调用ThreadPoolExecutor中特有的方法
-
Executors.newFixedThread(1)初始时为1,以后还可以修改,对外暴露的是ThreadPoolExecutor对象,可以强制转换调用setCorePoolSize等方法进行修改
-
5.线程池中的方法
// 执行某个任务 void execute(Runnable command) // 提交任务task,并返回值Future获得任务执行结果 Future> submit(Runnable task);Future submit(Callable task); Future submit(Runnable task, T result); void shutdown(); void shutdowNow();



