线程池的执行流程总结:
从创建线程池的参数分析:
1.提交新线程,线程池会在线程池中分配一个空闲线程,用于执行线程任务。
2.参数(int corePoolSize):核心线程数
- 如果线程池中不存在空闲线程,则线程池会判断当前"存活的线程数"是否小于核心线程数
- 如果小于核心线程数,线程池会创建一个新线程(核心线程)去处理新线程任务。
3.参数(BlockingQueue<Runnable> workQueue):工作队列
如果大于核心线程数,线程池会检查工作队列(workQueue),如果工作队列未满,则将该线程任务放入工作队列进行等待。线程池中如果出现空闲线程,将从工作队列中按照FIFO的规则取出1个线程任务并分配执行。
4.参数(int maximumPoolSize):最大线程数
如果工作队列已满,则判断线程数是否达到最大线程数maximumPoolSize.
- 如果当前"存活线程数"没有达到最大线程数maximumPoolSize,则创建一个新线程(非核心线程)执行新线程任务。
- 如果当前"存活线程数"已经达到最大线程数maximumPoolSize,直接采用拒绝策略处理新线程任务。
线程池的状态:
线程池的状态分为:RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED
RUNNING:运行状态,线程池一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0.该状态的线程池会接收新任务,并处理工作队列中的任务。
- 调用线程池的shutdown()方法,可以切换到SHUTDOWN关闭状态
- 调用线程池的shutdownNow()方法,可以切换到STOP停止状态
SHUTDOWN:关闭状态,该状态的线程池不会接收新任务,但会处理工作队列中的任务;
- 当工作队列为空时,并且线程池中执行的任务也为空时,线程池进入 TIDYING状态。
STOP:停止状态,该状态下的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在进行中的任务。
当线程池中的任务为空时,进入TIDYING状态;
TIDYING:整理状态,该状态表明所有的任务已经运行终止,记录的任务数量为0;
- terminated()执行完毕,进入TERMINATED状态;
TERMINATED:终止状态,该状态表示线程池彻底关闭。
线程池的类型:
FixedThreadPool
线程数固定的线程池,使用Executors.newFixedThreadPool()创建
线程池参数:
- 核心线程数和最大线程数一致
- 非核心线程线程空闲存活时间,即keepAliveTime为0
- 阻塞队列为无界队列LinkedBlockingQueue
java
public class Test05 {
public static void main(String[] args) {
//创建一个固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(4);
for(int i=0;i<6;i++) {
executorService.execute(new Task("线程"+i));
}
//关闭线程池
executorService.shutdown();
}
}
class Task implements Runnable{
private String taskName;
public Task(String taskName) {
this.taskName=taskName;
}
@Override
public void run() {
System.out.println("启动线程==>"+this.taskName);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束线程<="+this.taskName);
}
}
运行结果为:
启动线程==>线程0
启动线程==>线程3
启动线程==>线程2
启动线程==>线程1
结束线程<=线程2
结束线程<=线程1
结束线程<=线程0
结束线程<=线程3
启动线程==>线程5
启动线程==>线程4
结束线程<=线程4
结束线程<=线程5
执行分析:一次性放入6个线程,由于线程池只有固定的4个线程,因此,前4 个任务会同时执行,等到有空闲线程后,才会执行后面的两个任务。
工作机制:
a、提交线程任务
b、如果线程数少于核心线程数,创建核心线程执行任务
c、如果线程数等于核心线程数,把任务添加到LinkedBlockingQueue阻塞队列
d、如果线程执行完任务,去阻塞队列领取任务,继续执行
使用场景:适用于处理CPU密集型任务,确保CPU在长期被工作线程使用的情况下,尽可能少的分配线程,即适用执行长期的任务。
CachedThreadPool
可缓存线程池,线程数根据任务动态调整的线程池,使用Executors.newCacheThreadPool()创建
线程池参数:
- 核心线程数为0
- 最大核心线程数为Integer.MAX_VALUE
- 工作队列是SynchronousQueue同步队列
- 非核心线程空闲存活是时间为60秒
将线程池改为CachedThreadPool
java
ExecutorService executorService = Executors.newCachedThreadPool();
执行结果:
启动线程==>线程1
启动线程==>线程3
启动线程==>线程2
启动线程==>线程0
启动线程==>线程4
启动线程==>线程5
结束线程<=线程0
结束线程<=线程3
结束线程<=线程5
结束线程<=线程2
结束线程<=线程1
结束线程<=线程4
执行分析:由于这个线程池的实现会根据任务数量动态调整线程池的大小,所以6个任务可一次性全部同时执行。
工作机制:
a、提交线程任务
b、因为核心线程数为0,所有任务直接添加到SynchronousQueue工作队列
c、判断是否有空闲线程,如果有,就去取出任务执行
d、如果没有空闲线程,就创建一个新线程
e、执行完任务的线程,还可以存活60秒,如果在这期间,接到任务,可以继续存活下去;否则,被销毁。
使用场景:用于并发执行大量短期的小任务。
SingleThreadExecutor
单线程化的线程池,使用Executors.newSingleThreadExecutor()创建
线程池参数:
- 核心线程数为1
- 最大线程数也为1
- 阻塞队列是LinkedBlockingQueue
- 非核心线程数空闲存活时间为0秒
将线程池改为SingleThreadExecutor
java
ExecutorService executorService = Executors.newSingleThreadExecutor();
执行结果:
启动线程==>线程0
结束线程<=线程0
启动线程==>线程1
结束线程<=线程1
启动线程==>线程2
结束线程<=线程2
启动线程==>线程3
结束线程<=线程3
启动线程==>线程4
结束线程<=线程4
启动线程==>线程5
结束线程<=线程5
执行结果分析: 一次只执行一个线程,当该线程彻底执行完毕,再执行下一个线程。
使用场景:适用于串行执行任务的场景,将任务按顺序执行。
ScheduleThreadPool
能实现定时、周期性任务的线程池,,使用Executors.newScheduledThreadPool()创建
线程池参数:
- 最大线程数为Integer.MAX_VALUE
- 阻塞队列是DelayedWorkQueue
- keepAliveTime为0
将线程池改为ScheduledThreadPool
java
ExecutorService executorService = Executors.newScheduledThreadPool(3);
延迟3秒钟后,任务只执行1次
java
executorService.schedule(new Task("线程A"),3,TimeUnit.SECONDS);
延迟2秒钟后,每隔3秒执行一次
java
executorService.scheduleAtFixedRate(new Task("线程A"), 2, 3, TimeUnit.SECONDS);
executorService.scheduleWithFixedDelay(new Task("线程A"), 2, 3, TimeUnit.SECONDS);
FixedRate和Fixedelay的区别:
- FixedRate是指任务总是以固定时间间隔触发,不管任务执行多长时间;
- Fixedelay是指,上一次任务执行完毕后,等待固定的时间间隔,再执行下一次的任务;
使用场景:周期性执行任务,并且需要限制线程数量的需求场景。
线程池的配置参数总结
1.核心线程数:corePoolSize
int corePoolSize
核心线程数也就是线程池的最小线程数量。
- 核心线程会一直存活,不会被回收,除非设置了核心线程超时时间;
- 在创建线程池后,默认情况下,线程池中没有任何线程,调用excute()方法添加一个任务。
当线程池中没有空闲线程时,查看是否超过核心线程数:
- 线程数量小于核心线程数,则马上创建新的核心线程来执行线程任务。
- 线程数量大于核心线程数,则查看工作队列是否超出。
CPU密集型和IO密集型的区别:
(1)CPU密集型
CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是CPU Loading 100%,CPU要读/写I/O(硬盘/内存),I/O在很短的时间就可以完成,而CPU还有许多运算要处理,CPU Loading 很高。
在多重程序系统中,大部分时间用来做计算、逻辑判断等CPU动作的程序称之CPU bound。例如一个计算圆周率至小数点一千位以下的程序,在执行的过程当中绝大部分时间用在三角函数和开根号的计算,便是属于CPU bound的程序
CPU bound的程序一般而言CPU占用率相当高。这可能是因为任务本身不太需要访问I/O设备,也可能是因为程序是多线程实现因此屏蔽掉了等待I/O的时间。
(2)IO密集型
IO密集型指的是系统的CPU性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。
I/O bound的程序一般在达到性能极限时,CPU占用率仍然较低。这可能是因为任务本身需要大量I/O操作,而pipeline做得不是很好,没有充分利用处理器能力。
(3)先看下机器的CPU核数,然后在设定具体参数:
自己测一下自己机器的核数
System.out.printlnRuntime.getRuntime().availableProcessors());
即CPU核数 = Runtime.getRuntime().availableProcessors()
(4)分析下线程池处理的程序是CPU密集型还是IO密集型
CPU密集型:corePoolSize = CPU核数 + 1
IO密集型:corePoolSize = CPU核数 * 2
2.最大线程数:maximumPoolSize
int maximumPoolSize
是指线程池中允许的最大线程数量。
当工作队列已满,且存活线程数超过了核心线程数时,线程池判断**"** 存活线程数 " 是否超过最大线程数:
- 未超过最大线程数:创建新线程来执行该任务。
- 超过最大线程数:执行拒绝策略。
3.非核心的空闲线程的存活时间:keepAliveTime
long keepAliveTime
当线程数大于核心线程数时,空闲的线程等待新任务到达的最大时间,如果超过这个时间线程还没有需要执行的任务,该空闲线程就会销毁。
4.keepAliveTime的单位:unit
TimeUnit unit
keepAliveTime的单位,枚举类型的TimeUnit类。
5.阻塞工作队列:workQueue
BlockingQueue<Runnable> workQueue
在任务执行之前,用来存储任务的工作队列,此队列只保存由excute()方法提交的Runnable类型的任务。
当存活的线程数大于核心线程数,查看工作队列:
- 工作队列未满:将新的请求任务加入工作队列;
- 工作队列已满:线程池判断是否超过最大线程数。
5.1 ArrayBlockingQueue
基于数组有界阻塞队列,FIFO(先进先出)。
- capacity:队列初始化大小
- fair:表示该队列中的可重入锁是否公平,默认为false
当线程池中已经存在最大数量的线程时候,再请求新的任务,这时就会将任务加入工作队列的队尾,一旦有空闲线程,就会取出队头执行任务。
5.2 LinkedBlockingQueue
基于链表的误解阻塞队列,默认最大容量为Integer.MAX_VALUE,可认为是无限队列,FIFO(先进先出)。
指定工作队列大小,则最大线程数量的限制是有效的。
5.3 SynchronousQueue
可以将SynchronousQueue看作是一个没有容量的阻塞队列,它严格遵循FIFO(先进先出)的原则,但特殊的是,它不会保存任何元素,而是直接在不同的线程间进行传递。
6.线程工厂:threadFactory
ThreadFactory threadFactory
用于创建一个新线程时使用的工厂,可以用来设置线程名。
没有特别声明时,使用Executors工具类提供的默认线程工厂Executors.defaultThreadFactory()。
自定义线程工厂时,要实现ThreadFactory接口,重写newThread()方法。
7.拒绝策略:handler
RejectedExecutionHandler handler
当线程池内的线程被耗尽,并且工作队列已满,对于新提交的任务,将使用拒绝策略进行处理。
7.1 AbortPolicy:丢弃线程任务,并抛出异常
没有特别声明时,使用默认的拒绝策略defaultHandler
7.2 DiscardOldestPolicy:将工作队列的对头移除,线程池重新执行该线程任务
7.3 DiscardPolicy:直接丢弃该任务
7.4 CallerRunPolicy:线程池没有关闭时,线程自己调用run方法
7.5 自定义的拒绝策略:实现RejectedExecutionHandler接口
重写void rejectedExecution(Runnable r, ThreadPoolExecutor executor)方法