| 若是好看,请给个赞git
你有一个思想,我有一个思想,咱们交换后,一我的就有两个思想github
If you can NOT explain it simply, you do NOT understand it well enough面试
现陆续将Demo代码和技术文章整理在一块儿 Github实践精选 ,方便你们阅读查看,本文一样收录在此,以为不错,还请Star编程
防止并发编程出错最好的办法就是不写并发程序安全
既然多线程编程容易出错,为何它还经久不衰呢?服务器
A:那还用说,确定在某些方面有特长呗,好比你知道的【它很快,很是快】微信
我也很赞同这个答案,但说的不够具体网络
若是问你选择多线程的缘由就是一个【快】字,面试也就不会出那么多幺蛾子了。你有没有问过你本身多线程
想知道这两个问题的答案,咱们须要一个从【定性】到【定量】的分析过程并发
使用多线程就是在正确的场景下经过设置正确个数的线程来最大化程序的运行速度(我感受你仍是啥也没说)
将这句话翻译到硬件级别就是要充分的利用 CPU 和 I/O 的利用率
两个正确获得保证,也就能达到最大化利用 CPU 和 I/O的目的了。最关键是,如何作到两个【正确】?
在聊具体场景的时候,咱们必需要拿出咱们的专业性来。送你两个名词 buff 加成
一个完整请求,I/O操做能够在很短期内完成, CPU还有不少运算要处理,也就是说 CPU 计算的比例占很大一部分
假如咱们要计算 1+2+....100亿 的总和,很明显,这就是一个 CPU 密集型程序
在【单核】CPU下,若是咱们建立 4 个线程来分段计算,即:
线程1计算 [1,25亿)
...... 以此类推
线程4计算 [75亿,100亿]
咱们来看下图他们会发生什么?
因为是单核 CPU,全部线程都在等待 CPU 时间片。按照理想状况来看,四个线程执行的时间总和与一个线程5独自完成是相等的,实际上咱们还忽略了四个线程上下文切换的开销
因此,单核CPU处理CPU密集型程序,这种状况并不太适合使用多线程
此时若是在 4 核CPU下,一样建立四个线程来分段计算,看看会发生什么?
每一个线程都有 CPU 来运行,并不会发生等待 CPU 时间片的状况,也没有线程切换的开销。理论状况来看效率提高了 4 倍
因此,若是是多核CPU 处理 CPU 密集型程序,咱们彻底能够最大化的利用 CPU 核心数,应用并发编程来提升效率
与 CPU 密集型程序相对,一个完整请求,CPU运算操做完成以后还有不少 I/O 操做要作,也就是说 I/O 操做占比很大部分
咱们都知道在进行 I/O 操做时,CPU是空闲状态,因此咱们要最大化的利用 CPU,不能让其是空闲状态
一样在单核 CPU 的状况下:
从上图中能够看出,每一个线程都执行了相同长度的 CPU 耗时和 I/O 耗时,若是你将上面的图多画几个周期,CPU操做耗时固定,将 I/O 操做耗时变为 CPU 耗时的 3 倍,你会发现,CPU又有空闲了,这时你就能够新建线程 4,来继续最大化的利用 CPU。
综上两种状况咱们能够作出这样的总结:
线程等待时间所占比例越高,须要越多线程;线程CPU时间所占比例越高,须要越少线程。
到这里,相信你已经知道第一个【正确】使用多线程的场景了,那建立多少个线程是正确的呢?
面试若是问到这个问题,这但是对你理论和实践的统考。想彻底答对,你必需要【精通/精通/精通】小学算术
从上面知道,咱们有 CPU 密集型和 I/O 密集型两个场景,不一样的场景固然须要的线程数也就不同了
有些同窗早已经发现,对于 CPU 密集型来讲,理论上 线程数量 = CPU 核数(逻辑)
就能够了,可是实际上,数量通常会设置为 CPU 核数(逻辑)+ 1
, 为何呢?
《Java并发编程实战》这么说:
计算密(CPU)集型的线程刚好在某时由于发生一个页错误或者因其余缘由而暂停,恰好有一个“额外”的线程,能够确保在这种状况下CPU周期不会中断工做。
因此对于CPU密集型程序, CPU 核数(逻辑)+ 1
个线程数是比较好的经验值的缘由了
上面已经让你们按照图多画几个周期(你能够动手将I/O耗时与CPU耗时比例调大,好比6倍或7倍),这样你就会获得一个结论,对于 I/O 密集型程序:
最佳线程数 =
(1/CPU利用率)
=1 + (I/O耗时/CPU耗时)
我这么体贴,固然担忧有些同窗不理解这个公式,咱们将上图的比例手动带入到上面的公式中:
这是一个CPU核心的最佳线程数,若是多个核心,那么 I/O 密集型程序的最佳线程数就是:
最佳线程数 =
CPU核心数
*(1/CPU利用率)
=CPU核心数
*1 + (I/O耗时/CPU耗时)
说到这,有些同窗可能有疑问了,要计算 I/O 密集型程序,是要知道 CPU 利用率的,若是我不知道这些,那要怎样给出一个初始值呢?
按照上面公式,假如几乎全是 I/O耗时,因此纯理论你就能够说是 2N(N=CPU核数),固然也有说 2N + 1的,(我猜这个 1 也是 backup),没有找到具体的推倒过程,在【并发编程实战-8.2章节】截图在此,你们有兴趣的能够本身看看
理论上来讲,理论上来讲,理论上来讲,这样就能达到 CPU 100% 的利用率
若是理论都好用,那就用不着实践了,也就更不会有调优的事出现了。不过在初始阶段,咱们确实能够按照这个理论之做为伪标准, 毕竟差也可能不会差太多,这样调优也会更好一些
谈完理论,我们说点实际的,公式我看懂了(定性阶段结束),可是我有两个疑问:
没错,咱们须要定量分析了
幸运的是,咱们并非第一个吃螃蟹的仔儿,其实有不少 APM (Application Performance Manager)工具能够帮咱们获得准确的数据,学会使用这类工具,也就能够结合理论,在调优的过程获得更优的线程个数了。我这里简单列举几个,具体使用哪个,具体应用还须要你本身去调研选择,受篇幅限制,暂不展开讨论了
上面了解了基本的理论知识,那面试有可能问什么?又可能会以怎样的方式提问呢?
假设要求一个系统的 TPS(Transaction Per Second 或者 Task Per Second)至少为20,而后假设每一个Transaction由一个线程完成,继续假设平均每一个线程处理一个Transaction的时间为4s
如何设计线程个数,使得能够在1s内处理完20个Transaction?
可是,可是,这是由于没有考虑到CPU数目。家里又没矿,通常服务器的CPU核数为16或者32,若是有80个线程,那么确定会带来太多没必要要的线程上下文切换开销(但愿这句话你能够主动说出来),这就须要调优了,来作到最佳 balance
计算操做须要5ms,DB操做须要 100ms,对于一台 8个CPU的服务器,怎么设置线程数呢?
若是不知道请拿三年级期末考试题从新作(今天晚自习留下来),答案是:
线程数 = 8 * (1 + 100/5) = 168 (个)
那若是DB的 QPS(Query Per Second)上限是1000,此时这个线程数又该设置为多大呢?
一样,这是没有考虑 CPU 数目,接下来就又是细节调优的阶段了
由于一次请求不只仅包括 CPU 和 I/O操做,具体的调优过程还要考虑内存资源,网络等具体内容
看到这,有些同窗可能会认为,即使我算出了理论线程数,但实际CPU核数不够,会带来线程上下文切换的开销,因此下一步就须要增长 CPU 核数,那咱们盲目的增长 CPU 核数就必定能解决问题吗?
在讲互斥锁的内容是,我故意遗留了一个知识:
怎么理解这个公式呢?
这个结论告诉咱们,假如咱们的串行率是 5%,那么咱们不管采用什么技术,最高也就只能提升 20 倍的性能。
如何简单粗暴的理解串行百分比(其实均可以经过工具得出这个结果的)呢?来看个小 Tips:
Tips: 临界区都是串行的,非临界区都是并行的,用单线程执行临界区的时间/用单线程执行(临界区+非临界区)的时间就是串行百分比
如今你应该理解我在讲解 synchronized 关键字时所说的:
最小化临界区范围,由于临界区的大小每每就是瓶颈问题的所在,不要像乱用try catch那样一锅端
多线程不必定就比但线程高效,好比大名鼎鼎的 Redis (后面会分析),由于它是基于内存操做,这种状况下,单线程能够很高效的利用CPU。而多线程的使用场景通常时存在至关比例的I/O或网络操做
另外,结合小学数学题,咱们已经了解了如何从定性到定量的分析的过程,在开始没有任何数据以前,咱们可使用上文提到的经验值做为一个伪标准,其次就是结合实际来逐步的调优(综合 CPU,内存,硬盘读写速度,网络情况等)了
最后,盲目的增长 CPU 核数也不必定能解决咱们的问题,这就要求咱们严格的编写并发程序代码了
下一篇文章,咱们就来讲说,你熟悉又陌生的线程池问题
感谢前辈们总结的精华,本身所写的并发系列好多都参考了如下资料
加我微信好友, 进群娱乐学习交流,备注「进群」
欢迎持续关注公众号:「日拱一兵」
- 前沿 Java 技术干货分享
- 高效工具汇总 | 回复「工具」
- 面试问题分析与解答
- 技术资料领取 | 回复「资料」
以读侦探小说思惟轻松趣味学习 Java 技术栈相关知识,本着将复杂问题简单化,抽象问题具体化和图形化原则逐步分解技术问题,技术持续更新,请持续关注......