线程池
由于线程的创建与销毁性能开销较大,过多的线程会占用大量内存,所以需要**控制线程总量,线程统一管理,复用线程,**从而提高程序的效率和内存使用量,这就是使用线程池的好处
线程池的创建与停止
线程池创建参数
参数名 | 类型 | 描述 |
---|---|---|
corePoolSize |
int |
核心线程数量,线程池中完成初始化后,默认情况下,线程池中并没有任何线程,线程池会等待任务到来时再创建新线程去执行任务 |
maxPoolSize |
int |
最大线程数,线程池在任务队列满的情况下,就会在核心线程数上额外增加一些线程,但最多不能超过最大线程数 |
keepAliveTime |
long |
保持存活时间,当线程池当前线程数量超过核心线程数量时,多余线程的空闲时间超过该参数设置的时间,该线程就会被回收 |
workQueue |
BlockingQueue |
当核心线程都在处理任务时,会将任务暂时放入任务队列中,等待被处理,可使用以下几种任务队列:SynchronousQueue :没有容量的队列LinkedBlockingQueue :无线容量的队列ArrayBlockingQueue :固定容量的队列 |
Handler |
RejectedExecutionHandler |
当新任务提交关闭时、任务队列满且最大线程数也满时,新任务会被拒绝,有以下几种拒绝策略:AbortPolicy :抛出异常DiscardPolicy :默默的丢弃新任务DiscardOldestPolicy :丢弃任务队列中最老的任务,将新任务放入任务队列中CallerRunsPollicy :那个线程提交的任务,那个线程就自己执行该任务 |
threadFactory |
ThreadFactory |
线程池的线程是由该线程工厂来创建的,默认使用Executors.defaultThreadFactory() ,创建出来的线程都在同一个线程组,拥有相同优先级,不是守护线程,若自己指定线程工厂就可以改变这些新建线程属性 |
线程添加规则
- 线程数小于核心线程数,即使其他工作线程处于空闲状态,也会创建新线程来执行任务
- 若线程数大于等于核心线程数,但小于最大线程数,则将任务放入队列
- 若任务队列已满,并且线程数小于最大线程数,则创建一个新线程来运行任务
- 若队列已满,并且线程数大于等于最大线程数量,则拒绝该任务
线程池的创建
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1, //核心线程和最大线程数都是1
0, TimeUnit.SECONDS, //保持存活时间为0秒
new ArrayBlockingQueue<>(1), //任务队列容量为1
Executors.defaultThreadFactory(), //使用默认线程工厂
new ThreadPoolExecutor.CallerRunsPolicy()); //采用CallerRunsPolicy拒绝策略
for (int i = 0; i < 3; i++) {
threadPool.execute(() -> { //提交3个任务
System.out.println(Thread.currentThread().getName());
});
}
}
停止线程池
shutdown()
:关闭新的任务提交shutdownNow()
:向所有正在执行的线程发出中断信号,清空并返回未执行的任务队列isShutdown()
:判断新任务提交是否被关闭isTerminated()
:判断所有任务是否执行完毕awaitTermination(long time)
:等待指定时间后判断所有任务是否执行完毕,若线程池被关闭则会提前返回true
内置线程池
Executors.newFixedThreadPool(int nThread)
:返回固定线程数的线程池,核心线程数与最大线程数相等,无线容量任务队列,不会拒绝任务,可能会被撑爆Executors.newSingleThreadExecutor()
:返回固定线程数量为1的线程池,核心线程数与最大线程数相等,无线容量任务队列,不会拒绝任务,可能会被撑爆Executors.newCachedThreadPool()
:返回可缓存的线程池,核心线程数为0,最大线程数为整形最大值,没有容量的任务队列,每一个新任务都会创建一个线程去执行,线程都保持存活时间为60秒,当任务过多时可能会被撑爆Executors.newScheduledThreadPool(int corePoolSize)
:返回执行定时以及周期性任务的线程池,最大线程数为整形最大值,没有容量的任务队列,每一个新任务都会创建一个线程去执行,线程都保持存活时间为0秒,当任务过多时可能会被撑爆;延时任务使用schedule()
方法,周期任务使用scheduleAtFixedRate()
方法Executors.newWorkStealingPool()
:JDK1.8新增,返回任务中可含有子任务的线程池,每个子任务都会放到各自的任务队列中,空闲线程会窃取非空闲线程的任务,帮助非空闲线程执行,从而提高了效率,但是这样就不能保证任务的执行顺序了
任务的AOP
继承ThreadPoolExecutor
类重写构造方法,重写beforeExecute()
和afterExecute()
方法实现任务的AOP
public class MyThreadPoolExecutor extends ThreadPoolExecutor {
public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
System.out.println("任务执行前");
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
System.out.println("任务执行后");
}
public static void main(String[] args) {
MyThreadPoolExecutor myThreadPoolExecutor = new MyThreadPoolExecutor(1, 1,
0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>());
for (int i = 0; i < 2; i++) {
myThreadPoolExecutor.execute(() -> {
System.out.println("任务执行中...");
});
}
}
}
线程池原理
线程池的体系结构
线程池的状态
- RUNNING:接收新任务,并处理任务队列中的任务
- SHUTDOWN:不接收新任务,但处理任务队列中的任务
- STOP:不接收新任务,也不处理任务队列中的任务,并中断正在运行的任务
- TIDYNG:所有任务都已经完成,即将运行
terminate()
钩子方法 - TERMINATEA:
terminate()
钩子方法运行完毕
线程池的运行原理
线程池注意事项
合适的线程数
- CPU密集型任务:线程数量设置为CPU核心数量的1到2倍,比如加密解密、计算hash等
- 耗时IO型任务:线程数量设置为CPU核心数量的很多倍,从而保证线程空闲时可以衔接上,具体可参考使用公式
合适的任务
- 避免任务堆积、线程数过度增加:任务、线程过多可能会导致内存溢出
- 避免线程泄露:任务执行结束线程却无法回收
Comments NOTHING