From 614ed50b2741585b47c558d1bbb93f245f14aac5 Mon Sep 17 00:00:00 2001 From: Omooo <869759698@qq.com> Date: Fri, 22 Mar 2019 09:47:29 +0800 Subject: [PATCH] =?UTF-8?q?Update=20=E7=BA=BF=E7=A8=8B=E3=80=81=E7=BA=BF?= =?UTF-8?q?=E7=A8=8B=E6=B1=A0.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blogs/Java/并发/线程、线程池.md | 55 ++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/blogs/Java/并发/线程、线程池.md b/blogs/Java/并发/线程、线程池.md index 6d3c556..f7a5e39 100644 --- a/blogs/Java/并发/线程、线程池.md +++ b/blogs/Java/并发/线程、线程池.md @@ -5,19 +5,28 @@ Java 线程、线程池 #### 目录 1. 前言 -2. 线程的创建 +2. 线程 + - 线程的创建 + - 线程生命周期 3. Callable 与 Future 4. 线程池 + - 度量性能 + - 线程数量分配 + - ThreadPoolExecutor 5. 参考 #### 前言 +#### 线程 - -#### 线程的创建 +##### 线程的创建 我们知道,线程的创建有两种方式:一种是继承 Thread 类,另一种是实现 Runnable 接口。但是这两种方式有一个共同的缺陷,那就是在执行任务后无法获取执行结果(两者 run 方法的返回类型都是 void )。于是在 JDK 5 就引入了 Callable 和 Future。 +##### 线程生命周期 + +[线程生命周期]() + #### Callable 与 Future ##### Callable @@ -85,7 +94,41 @@ public interface Future { #### 线程池 -线程池是为了线程的复用,避免重复创建和销毁线程所带来的性能损耗。 +首先,为什么要使用多线程呢? + +你肯定会想到,线程池是为了线程的复用,避免重复创建和销毁线程所带来的性能损耗。这种说法并不严谨,在提升性能之前,首先要问题是:如何度量性能? + +##### 度量性能 + +度量性能的指标很多,但是有两个指标是最核心的,它们就是延迟和吞吐量。延迟指的是发出请求到收到响应这个过程的时间;延迟越短,意味着程序执行的越快,性能也就越好。吞吐量指的是在单位时间内能处理请求的数量;吞吐量越大,意味着程序能处理的请求越多,性能也就越好。 + +要想 "降低延迟,提供吞吐量",有两个方向,一个方向是优化算法,另一个方向是**将硬件的性能发挥到极致**。前者属于算法范畴,后者则是和并发息息相关了。在并发编程领域,提升性能本质上就是提升硬件的利用率,再具体点来说,就是提升 I/O 的利用率和 CPU 的利用率。 + +估计这个时候你会有疑问,操作系统不是已经解决了硬件的利用率问题了嘛?的确是这样,例如操作系统已经解决了磁盘和网卡的利用率问题,利用中断机制还能避免 CPU 轮询 I/O 状态,也提升了 CPU 的利用率。但是操作系统解决硬件利用率问题的对象往往是单一的硬件设备,而我们的并发程序,往往需要 CPU 和 I/O 设备相互配合工作。也就是说,我们需要解决 CPU 和 I/O 设备综合利用率的问题。关于这个综合利用率的问题,操作系统虽然没有办法完美解决,但是却给我们提供了方案,那就是:多线程。 + +##### 线程数量分配 + +那么创建多少线程才合适呢? + +如果 CPU 和 I/O 设备的利用率都很低,那么可以尝试通过增加线程来提高吞吐量。 + +在单核时代,多线程主要就是用来平衡 CPU 和 I/O 设备的。如果程序只有 CPU 计算,而没有 I/O 操作的话,多线程不但不会提升性能,还会使性能变得更差,原因是增加了线程切换的成本。但是在多核时代,纯计算型的程序也可以利用多线程来提升性能。这是为什么呢?因为利用多核可以降低响应时间。 + +比如要计算 1~100亿 的值,如果在四核的 CPU 上利用四个线程执行,线程 A 计算 [1,25亿),线程 B 计算 [25亿,50亿),线程 C 计算[50亿,75亿),线程 D 计算[75亿,100亿],之后在汇总,那么理论上应该比一个线程计算快四倍。一个线程,对于四核的 CPU,CPU 利用率只有 25%,而四个线程,则能够将 CPU 的利用率提高到 100%。 + +对于 CPU 密集型计算,多线程本质上是提升多核 CPU 的利用率,所以对于一个四核的 CPU,每个核一个线程,理论上创建四个线程就可以了,再多创建线程只是会增加线程切换的成本。所以,**对于 CPU 密集型计算场景,理论上 "线程的数量 = CPU 核数" 就是最合适的。不过在工程上,线程的数量一般会设置为 " CPU 核数 +1 "。**这样的话,当线程因为偶尔的内存页失效或其他原因导致阻塞时,这个额外的线程可以顶上,从而保证 CPU 的利用率。 + +对于 I/O 密集型的计算场景,最佳的线程数是与程序中 CPU 计算和 I/O 操作的耗时比相关的,可以总结为: + +```java +线程数 = 1 + ( I/O 耗时 / CPU 耗时 ) +``` + +不过上面这个公式只针对单核 CPU 的,至于多核 CPU,只需要等比扩大即可: + +``` +线程数 = CPU 核数 * [ 1 + ( I/O 耗时 / CPU 耗时 )] +``` ##### ThreadPoolExecutor @@ -168,4 +211,6 @@ public ThreadPoolExecutor(int corePoolSize, #### 参考 -[Java并发编程:线程池的使用](https://www.cnblogs.com/dolphin0520/p/3932921.html) \ No newline at end of file +[Java并发编程:线程池的使用](https://www.cnblogs.com/dolphin0520/p/3932921.html) + +[Java线程(中):创建多少线程才是合适的?](https://time.geekbang.org/column/article/86666) \ No newline at end of file