谈谈关于线程池的技术
三大方法
-
newSingleThreadScheduledExecutor()方法
-
这个方法可以通过调用Executors工具类来调用,其底层是通过调用ThreadPoolExecutor这个类来进行实现让线程池核心线程数为1来实现,底层只有一个参数如图:
-
newFixedThreadPool(最大线程数)方法
- 同样是通过调用Executors工具类实现,底层也是ThreadPoolExecutor这个类,但是它可以创建一个固定的线程池的大小,如图:
- 同样是通过调用Executors工具类实现,底层也是ThreadPoolExecutor这个类,但是它可以创建一个固定的线程池的大小,如图:
-
newCachedThreadPool()方法
-
同样是通过调用Executors工具类实现,底层也是ThreadPoolExecutor这个类,但是它是可以伸缩的,遇到需要处理的资源多了就扩大线程池容量,而遇到资源少了便缩小线程池容量,相对灵活,如图:
七大参数
- int corePoolSize (核心线程池大小)
- int maximumPoolSize (最大核心线程池大小)
- long keepAliveTime (超时了没有人调用就会释放)
- TimeUnit unit (超时的单位)
- BlockingQueue< Runnable > workQueue (阻塞队列)
- ThreadFactory threadFactory (线程工厂,创建线程的,一般不需要动)
- RejectedExecutionHandler handle (拒绝策略)
阿里手册对线程池创建的一些要求
相信大家都听说过阿里手册对我们写代码提了一些要求和规范吧,反正我是下了IDEA的插件,时刻提醒自己要规范的编写代码,根据阿里手册的要求,线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险,说明Executors返回的线程池对象的弊端如下:
- FixedThreadPool和SingleThreadPool允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM
- CachedThreadPool和ScheduleThreadPool允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM
总的来说其实就是让写代码的同学时刻注意不要出现内存溢出的情况,让自己去设置那些你需要的参数
四种拒绝策略
刚才提到的七大参数的最后一个就是拒绝策略,这个一般就是线程池和队列都满了的情况才会去采用的办法,有如下四种拒绝策略:
- new ThreadPoolExecutor.AbortPolicy() (队列满了,还有要处理的资源,不予处理,并且会抛出异常,为默认的拒绝策略)
- new ThreadPoolExecutor.CallerRunsPolicy() (从哪来的资源就去哪处理)
- new ThreadPoolExecutor.DiscardPolicy() (队列满了,丢掉任务,但是不会抛出异常)
- new ThreadPoolExecutor.DiscardOldestPolicy() (队列满了,试探最早的是否结束,如果结束就跟进,没结束就不予处理,也不会抛出异常)
池的大小如何去设置
- CPU密集型:电脑为几核就定义为几,可以保持CPU的效率最高
- IO密集型: 要大于你所判断的程序中十分耗IO的线程
- 这里额外说一下如何获取自己电脑的CPU核数:
//获取电脑CPU的核数
System.out.println(Runtime.getRuntime().availableProcessors());
下面附加一些我测试的代码:
package com.czu.pool;
import java.util.concurrent.*;
/**
* Executors 工具类、三大方法
* 使用了线程池之后,使用线程池创建线程
* @author 87682
*/
public class Demo01 {
public static void main(String[] args) {
//单个线程
//ExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();
//创建一个固定的线程池的大小
//ExecutorService threadPool = Executors.newFixedThreadPool(5);
//可伸缩的,遇强则强,遇弱则弱
//ExecutorService threadPool = Executors.newCachedThreadPool();
//最大线程到底该如何定义
//1.CPU 密集型 几核 就定义为几,可以保持CPU的效率最高
//2.IO 密集型 > 判断你程序中十分耗IO的线程,
// 程序 15个大型任务 IO十分占用资源
//获取电脑CPU的核数
System.out.println(Runtime.getRuntime().availableProcessors());
//自定义线程池
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
try {
//最大承载量:Deque + max
//超出最大承载会抛出异常:java.util.concurrent.RejectedExecutionException
for (int i = 1; i <= 9; i++) {
//使用了线程池之后,使用线程池创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
}
}
其实就是测试一些简单基本的自定义线程池的办法,上面也阐述了里面的一些细节,要理解,线程池的取用就是一部分在阻塞队列,一部分在核心线程工作,而当阻塞队列满的时候会开启最大线程数来处理,如果最大线程数和阻塞队列都满了,那么就涉及拒绝策略,一切均在参数里面可以体现,线程池的最核心的部分也就是这些了吧
总结
众所周知程序运行的本质就是占用系统的资源,优化资源的使用,所谓的池化技术就是事先去准备好一些资源,有需要用的时候就到池子里面去取,使用完毕后再还到池子里
线程池的好处也是显而易见的:
- 可以降低资源的消耗
- 提高响应的速度
- 方便管理(线程复用,可以控制最大并发数,管理线程)