【衔接上一章 【并发编程五:Java中的线程池(2)-线程的实现原理】】
学习路线- 1.9工作中线程池的应用
- 1、局部 & 全局单例;
- 2、多个业务线程池;
- 1.10如何合理设置线程池大小?
- 1、《Java Concurrency in Practice》即《Java并发编程实践》,书中第八章8.2节指出:
- 2、《Programming Concurrency on the JVM》即《Java虚拟机并发编程》,书中第二章2.1节指出:
- 国内采用的一些方案:
- 这些公式可以吗?
- 2核cpu示例:
- 1.11动态线程池
- Spring Cloud Alibaba版本说明
- Nacos运行环境部署
根据要执行的任务的特点,使用多个线程池,把相同类型的任务放到同一个线程池中,可以起到隔离的作用,避免一个线程池中的线程阻塞或占满时影响到其它线程池执行;
因为不同业务的特征不一样,有的业务是CPU密集型的,有的业务是IO密集型的,有的任务执行耗时比较长,有的任务执行耗时比较短,有的任务可能是连数据库,有的任务可能是调第三方接口等等,分开使用不同的线程池。
线程池大小设置主要来自于国外的两本书:
1、《Java Concurrency in Practice》即《Java并发编程实践》,书中第八章8.2节指出:公式:
其中:
Ncpu = cpu的核心数 ,Ucpu = cpu的使用率(在0~1之间)
W = 线程等待时间,C = 线程计算时间
8 * 100% * (1+60/40) = 20
8 * 100% * (1+80/20) = 40
公式:
线程数 = CPU可用核心数 / (1 - 阻塞系数)
其中阻塞系数 = 阻塞时间 /(阻塞时间+计算时间),取值为0 - 1 之间;
计算密集型任务的阻塞系数为0,IO密集型任务的阻塞系数接近1,但阻塞系数不可能是1,是1的话表示完全阻塞,这是不存在的;
如果阻塞系数是0.6,就表示有60%的时间在阻塞,有40%的时间在执行任务;
8 / (1-0.6) = 20
8 / (1-0.8) = 40
得到的规律:线程在执行的时候,如果等待时间/阻塞时间比较多,那么就应该设置更多的线程数;
如果等待时间/阻塞时间比较少,那么就应该设置更少的线程数;
- 1、CPU密集型:
任务需要大量使用CPU进行运算,但是没有阻塞,CPU一直高速运行; 该情况下应尽可能减少CPU对线程的切换,所以要使线程数尽可能少; 线程数 = CPU核心数 + 1; CPU核心数:Runtime.getRuntime().availableProcessors(); 这里的+1,比CPU核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响,一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间;当然+1也可能导致多一个cpu上下文切换;
- 2、IO密集型:
- 任务需要进行大量的IO操作,那么就会有大量的阻塞等待, - CPU大部分时间是在阻塞,该情况下应尽可能减少阻塞所带来的消耗, - 所以要使线程数尽可能多; 公式:线程数 = CPU核心数 * 2
- 3、 另外的公式
-** - 线程数 =((线程等待时间+线程CPU时间)/ 线程CPU时间 )* CPU核心数; - ** 比如平均每个线程CPU运行时间为0.4s,线程等待时间为0.6s,CPU核心数为8, 那么根据上面公式计算:((0.6+0.4)/0.4)*8=20; 线程等待时间所占比例越高,需要越多线程,线程CPU时间所占比例越高, 需要越少线程;这些公式可以吗?
综上,线程池大小的设置没有固定公式,不能设置过大也不能设置过小,我们可以根据业务场景,从上面三个公式中选择一个得到一个大概的线程数量,然后通过压测,逐渐“增大线程数量”和“减小线程数量”,然后观察整体的处理时间变化,最终确定一个具体的线程数量,让任务处理速度最快;
2核cpu示例://基于Executor框架实现线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
4,
4,
5,
TimeUnit.SECONDS,
new ArrayBlockingQueue(256),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
threadPoolExecutor.execute(() -> System.out.println(1));
任务执行一般是100-300ms左右,取中间值200ms,客户端能容忍10s超时;
10s * 1000 = 10000ms / 200ms = 50个 * 4个线程 = 200个 -->256;
maximumPoolSize最大线程数在生产环境上我们往往设置成corePoolSize一样,这样可以减少在处理过程中创建线程的开销;
线程池的任务队列本来起缓冲作用,任务队列长度取决服务端处理能力以及客户端能容忍的超时时间;如果响应时间要求较高的系统可以设置为任务队列长度为0;
任务执行时间短,可以把等待队列弄大一点,任务执行耗时长,等待队列应该调小一点;
线程池大小的设置没有完美公式,实际评估和压测得到的合理线程池参数在实际线上环境运行过程中也许不一定合理,怎么样快速地修改线程池的参数呢?
Spring Cloud Alibaba版本说明https://github.com/alibaba/spring-cloud-alibaba/wiki/
Nacos运行环境部署Nacos官网:https://nacos.io/
- 1、下载nacos最新的二进制压缩包;
下载地址:https://github.com/alibaba/nacos/releases - 2、解压下载下来的nacos最新的二进制压缩包;
tar -zxvf nacos-server-1.4.2.tar.gz
cd nacos/bin - 3、启动nacos server
./startup.sh -m standalone
注:单机环境必须带-m standalone参数启动,否则无法启动,不带参数启动的是集群环境; - 4、启动日志:/usr/local/nacos/logs/start.out
- 5、访问:http://ip:8848/nacos 默认用户名密码:nacos/nacos
【衔接下一章【并发编程七:Java中的线程池(4)-线程池规范、监控、关闭】】



