池化技术到达有多牛?看了线程和线程池的对比吓我一跳!

云栖号资讯:【点击查看更多行业资讯】
在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来!


情商高的人是能洞察并照顾到身边所有人的情绪,而好的文章应该是让所有人都能看懂。

尼采曾经说过:人们2 n 8 4 ^ m ) ] n无法理解他没有经历过的事情。因此我会试着把技术文章写的尽量具象化一些,力求让所有人都能看懂,所以在正式开始之前,我们先从两个生活事例说起。

尼采帅j o Y t V照:

池化技术到达有多牛?看了线程和线程池的对比吓我一跳!

唠嗑:之前一直以为尼采是中国的某位圣人,大体和庄子差不多,后来才知道原来是一位老外,惊了个呆。

生活案o g l z ( a _例 1

早些年间,某宝双“11”突然爆火} c p,然后无数个男男女女疯狂“剁手”,然而最痛苦的并不是“剁手”之后吃“灰”的日子,而是漫长而又揪心的等待快递小哥的日子。

池化技术到达有多牛?看了线程和线程池的对比吓我一跳!

为了缓解彼此的“痛苦”(快递公司的电话被打爆,用户等得不耐烦),快递公司后面就变“聪明”了,每当购物节将要来临之前,快递公司会预先准备好充# ^ z 1 . l V足的人和车,以迎接扑面而来的订单。

至此,当我们再遇到各种购物节,就再也不用每天盯着手机煎熬的等待快递小哥了。

生活案例 2

小美是一家公司的 HR,每年年初是小美c b % A i E最头疼的日子了。因为年初有大量的员工离职,因此小美需要一边办理离职员工X L 3 g ^ [ ?的手续,一边疯狂的招人,除了这些工作之外,小w 0 M J ) e # o o美还要t ` p p 2 8 . z忍受来自各部门和大 BOSS 的间歇性催促,这些都让小美痛苦不已。

于是为了应对每年年初的这种囧境,小美也变聪( . B G P明了,她每年年末的时候都会预先招聘一些员工,以备来年的不时之需。b 4 w E ) N P

自从用了这招N n { B t p U /之后(提前招人),小美从此过上了幸福的生活。

概念

池化技术指的是提前准备一些资源,Z 1 S在需要时可以重复使用这些预先准备的资源。

也就是说池化技术有5 Y B g x y 2 Q O两个优点:

  • 提前创建;
  • 重复利用。

池化技术优点分析1 X 2 6

以 Java 中的对象创建来说,在对象创建时要经历以下步骤:

1.根据 new 标识符后面的参数,在常量池查找类的符号引用;

2.如果没找到符号应用(类并未加载),进行类的M : ^ / a加载、解析、初. 4 u _ 4始化等;

3.虚拟机为对象在堆中分配内存,并将分配的内存初始化为 0,针对对象头,建立相应的描述结构(耗时操作:需要查找堆中的空闲区域,修改内存分配状态等);

4.调用对象的初始化方法(耗时操作:用户的复杂的逻辑验证等操作,如IO、数值计算是否符合规定等)。

从上述的流程中可以看出,创建一个类需要经历复杂且耗时的操作,因此我们应该尽量复用已有的类,以确保程序的高效运行,当然如果能够提前创建这些类就再好不过了,而这些功能都可以用池化技术来实现。

池化技术常见应用

常见的池化技术的使用有:线程池、内存池、数据库连接池、HttpClientL 7 e , G + & 1 f 连接池等,下面分别来看。

1.线程

线程池的原理很简单,类似于操作系统中的缓冲区的概念S x h ( & O。线程池中会先启动若干数量的线程,这些线程都处于睡眠状态。当客户端有一个新的请求时,就会唤醒线程池中的某一个睡眠的线程,让它来处理客户端的这个请求,当处理完这个请求之后,线程又@ 5 l G Q ` I处于睡眠的状态。A j ( k = 5 T

线程池能很高地提升程序的性能。比如有一个省级数据大集中的银行网络中心Q t w,高( J Z 7 @ S峰期每秒的客` _ q 0 ^ # } r户端请求并发数超过100,如果为n & N y每个客户端请求创建一个新的线程的话,那耗费的 CPU 时1 8 = $ ! @ i F间和内存都是十分惊人的,如果采用一个拥有 200 个线程的线程池,那将会X ^ r ,节约大量的系统资源,使得更多的 CPU 时间和内存用来处理实际的商业应用,而不是频繁的线程创建和销毁。

池化技术到达有多牛?看了线程和线程池的对比吓我一跳!

2.内存池

如何更好地管理应用程序内存的使用,同时提高内存使用的频率,这时值得每一个开发人员深思的问题。内存池(Memory Pool)就X N B 7提供了一个比较可行的解决方案。

内存池在创建的过程中,会预先分配足够大的内存,形成一个初步的内存池。然m v q N M P后每次用户请求内存的时候,就会返回内w , U v z T w存池中的一块空闲的内存,并将这块内存的标志置为已使用。当内存使用完毕释放内存的时候,也不是真正地调用 free 或 deletV ) v ne 的过程,而是把内存放回内存池的过程,且放回的过程要把标志置为空W ; } + 4闲。最后,应用程序结束就会将内存池销毁,将内存池中的每一块内存释放。

内存池的优点:

  • 减少内存碎片的产生,这个优点可以从创建内存池的过b e Z Y e h *程中看出,当我们在创建内存池的时候,分配的都是一块块比较规整的内存块,减少内存碎片的产| I r L g [生。
  • 提高了内存的使用频率。这个可以从分配内存和释放内存j C + (的过程中看出。每次的分配和释放并不是去调用系统提供的函数或操作符去操作实际的内存,而是在复用内存池中的内存。

内存池j = . @ i 1 P _ 4的缺点:

会造成内存的浪费,因为要使用内存池需要在一开始分配一大块闲置的内存,而这些内存不一定全部被用到。

3.数据库连接池

数据库连接池的基本思想是在系统初始化的时候将数据库连接作为对象存储在内存中,当用户需要访问数据库的时候,并非建立一个l F ?新的连接,而是从连接池中取出一个已建立的空闲连接Y $ ` x ` ,对象。在使用完毕后,用户也不是将连接关闭,而是将连接放回到连接池中,以供下一个请求访问使用,而这些连接的建立、断开都是由连接池自身来管理的。

同时,还可以设置连接池的参数来控制连接池中的初始连接数、连接的上下限数和每个连接的最大使用次数、最大空闲时间等。当然,也可以通过连接池自身的管理机制来监视连接的数量、使用情况等。

池化技术到达有多牛?看了线程和线程池的对比吓我一跳!

4.HttpClient 连接池

HttpClient 我们经常用来进行 HTTP 服务访问。我们的项目中会有一个获取任务执行状态的功能使用 HttpCliO S f t L % t Aent,一秒钟请求一次,经常会出现 Conection Reset 异常。经过分析发现,问题是出在 HttpClient 的每次请求都会新建一个连接,当创建连接的频率比关闭连接的频率大的时候,就会导致系统中产生大量处于 TI ~ME_CLOSED 状态的连接,这a h F f i ?个时候使用连接池复用连接就能解决这个问题。

实战:线程 VS 线程池

测试一下线程和线程8 H K N w D { E D池执行的时间差距有多大,测试代码如下:

import java.util.concurrent.Lin: s skedBlockingDeque;
import java.util.concuV M j I Lrr- G _ Sent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 线程池 vs 线程 性能对比
*/
public class ThreadPoolPr & 9 ) ^ 7 k K -erformance {
// 最大执行次数
public st? L | i 2atic final int m6 o # +axCount = 1000;
public static void main(String[] args) throws InterruptedException {
/A U W @ ] [ C x L/ 线程测试代码
ThreadPerformanceTest();
// 线程池测试代码
ThreadPoolPerformanceTest();
}
/**
* 线程池性能测试
*/
p] - a @ { O 6 | 3rivate static void Threa| b V ( YdPoolPerformanceTest() thron (  G & ^ws InterruB { $ lptedException {
// 开始时间
long stime = System.currentTimeMillis();
// 业务代码
ThreadPoolExecutor tp = new ThreadPoolExecutor(10, 10, 0,
TimeUnit.SECONDS, new LinkedBlockingDeque<>());
for (int! z ! U i = 0; i < maxCount; i++) {
tp.execute(new PerformanceRunnable());
}
tp.shutdown();
tp.awaitTermination(1, TimeUnit.SECONDS);  // 等待线程6 % L v U G Q ] O池执行完成
// 结束时间
lo- 6 H J n P w Bng etimep { @ 9 h n ) = System.curreY ] FntTimeMillis();
// 计算执行时间
System.out.printf("线程池执行时长:%d 毫秒.",7 { / 9 (etime - stime));
System.q L _ j H R ]out.println();
}
/**
* 线程性a z . : ( d R $ 4能测试
*/w j W
private static void ThreadPerformanceTest() {
// 开始时间~ / w
lo@ b ! E z k } /ng stime = Syst# s l 7em.cuS  = WrrentTimeMillis();
// 执行业务代码
for (int i = 0; i &lz U 1t; mb b r A YaxCount; i++) {
Thread td = new Thread(new PerformanceRunnableB h G | w %());
td.start~ ] p i ` e Q ` O();
try {
td.join(); // 确保线程执行完成
} catch (InterruptedException e) {
e.printStackTrace()@ ~ U T R q;
}
}
// 结束时间
lok O a q *ng etim9 p S u J Ze = Systeq ? j z @ R b Lm.currentTimeMillis();
// 计算执行时间
System.out.printf("线程执行时长:%d 毫秒.", (etime - stime));
System.out.println();
}
// 业务执行类
static class Perfz L 1 T .o! H  e w @ Y ormancU O meRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < maxCounm / n / / P Et; i++) {
long num = i * i + i;
}
}
}
}

以上程序的执行结果如下图所示:

池化技术到达有多牛?看了线程和线程池的对比吓我一跳!

为了防止执行的先后顺序影响测试结果,下面我将线程池和线程调用方法打个颠倒,u F B ] y R % f执行结果如下图所示:

池化技术到达有多牛?看了线程和线程池的对比吓我一跳!

总结

从线程和线程池的测试结果来看,6 ^ p L J a y N当我们使用池化技术时,程Y [ { h /序的性能可以提升 10 倍。此测试结果并不代表池化技术的性能量化结果,因为测试结果受F 3 : #执行方法和循环次数的影响,但巨大的性能差异足以说明池化技术的优势所在。

【云栖号在线课堂】每天都J @ 7 x M G &有产品技术专家分享!
课程地址:https:/k / D O/yqh.al$ r )iyun.coml j l X O Z/live

立即加入社群,与专家面对面,及时了解课程最新动态!
【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK

原文发布时间:2020-07-28
本文作者:Java中文社群
u 2 % 3 x k ! - /文来自:“d v f掘金”,了解相关信息可以关注“掘金”