并发与高并发系列第一集-基础与概念

开发 架构
通常我们谈论并发的时候,更多的关注点在于线程安全,但是讨论高并发时,关注点不仅仅是线程安全问题,而是如何在短时间内处理大量请求,保证系统响应时间和吞吐量的可靠,更多关注的是稳定性问题(SRE),高并发涉及的是完整的系统知识,线程安全只是其中一小部分。

[[404291]]

本文转载自微信公众号「安琪拉的博客」,作者安琪拉。转载本文请联系安琪拉的博客公众号。

面试官:看你简历上写,最近正在写并发编程方面的博客,是吧?

安琪拉:闲来无事,看看闲书,写写段子,承蒙读者厚爱,有此打算。

面试官:少跟我这拽文,“闲来无事”?阿里不用996吗?

安琪拉:修福报,你知道吗?..... 技术人的日常,能算996吗?

面试官:算了算了,还是聊正题,你先跟我讲讲什么是并发?

安琪拉:并发就是存在两个或多个线程,这些线程同时操作相同的物理机中的资源。

面试官:那并发跟并行有什么区别呢?

安琪拉:举个生活中的例子就懂了:

你在打王者荣耀,这个时候女朋友找你视频,你一直打完王者荣耀才接,说明你不支持并发(也不支持并行);

你在打王者荣耀,这个时候女朋友给你发了微信,你退出王者荣耀,回完微信再回到王者,微信和王者间来回切换,说明你支持并发,但不支持并行;

你在打王者荣耀,这个时候女朋友给你打电话,你边打荣耀边接电话,说明你支持并行。

并行的关键点是物理的“同时”,我们在单核CPU的时候,既能写代码也能听歌,这个多线程实际是基于操作系统根据CPU时间片做的任务轮转,是伪“同时”,只能说是并发,不能算并行,但是多核CPU可以支持每个核同时运行任务,是真实的“同时”,是并行。

Erlang 之父 Joe Armstrong 画了一张图解释了并发与并行的区别,Concurrent (并发),Parallel (并行)。

并发允许二队小孩轮流使用咖啡机,并行是同时存在二台咖啡机,二队小孩同时使用,不冲突。

面试官:那高并发呢?你了解高并发吗?

安琪拉:【心里想,该来的还是来了,要造火箭了】

你说High Concurrency(高并发)是吧(先拽句英文)。

通常我们谈论并发的时候,更多的关注点在于线程安全,但是讨论高并发时,关注点不仅仅是线程安全问题,而是如何在短时间内处理大量请求,保证系统响应时间和吞吐量的可靠,更多关注的是稳定性问题(SRE),高并发涉及的是完整的系统知识,线程安全只是其中一小部分。

高并发是现在互联网设计系统中需要考虑的一个重要因素之一,通常来说,就是通过严谨的设计来保证系统能够同时并行处理很多的请求。这就是大家常说的「 高并发 」。也就是说系统能够在某一时间段内提供很多请求,但是不会影响系统的性能。如果想设计出高可用和高性能的系统,就应该从很多的方面来考虑,例如应该从硬件、软件、编程语言的选择、网络方面的考虑、系统的整体架构、数据结构、算法的优化、数据库的优化等等多方面。这其中的每一点展开来说都要说很多的知识,安琪拉会在后续课程更新这部分内容。

面试官:那你跟我讲讲你们系统的QPS有多少?

安琪拉:大促场景能有个10W+的QPS,日常业务高峰期也有2W+,其他时间几千。

其实对于大部分的系统,几十、几百很正常,QPS能过千的就已经不低了,有的业务会有峰值,QPS稳定过万的系统实际中不多,所以大家日常可以关注一下自己系统的QPS,这个问题面试经常会问。

面试官:一般我们有什么工具可以模拟并发请求呢?

安琪拉:PostMan、Apache Bench(AB)、Jmeter,推荐使用Jmeter。

面试官:那你能写段代码,演示一下并发安全的问题吗?

安琪拉:可以啊。笔递给我一下,顺便帮我拿下A4纸。

  1. public class ConcurrencySafeTest { 
  2.  
  3.     private static int counter = 0; 
  4.  
  5.     public static void main(String[] args) { 
  6.         //使用线程池 
  7.         ThreadPoolExecutor threadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool(); 
  8.         //提交2000个任务 
  9.         for(int i = 0; i < 2000; i++) { 
  10.             threadPool.submit(new Add()); 
  11.         } 
  12.         threadPool.shutdown(); 
  13.         System.out.println(counter); 
  14.     } 
  15.  
  16.     static class Add implements Runnable { 
  17.         @Override 
  18.         public void run() { 
  19.             counter++; 
  20.         } 
  21.     } 

我们进行计数操作,执行2000次,预期的执行结果应该是2000,但是实际执行结果如下:

  1. 1971 
  2. 1987 

因为《并发》系列是从基础开始讲的,上面的代码部分内容涉及到后面的一些内容,比如线程池和线程的使用,这里只要大致了解并发的安全问题,后面会有详细说明,后面面试官的问题作为扩展阅读。

面试官:看到你代码中用了CachedThreadPool,那2000次任务执行,CachedThreadPool 线程池创建了多少个线程?

安琪拉:答案是不确定,CachedThreadPool 缓存了线程(复用线程),没有让任务排队,来一个任务,要么复用已有线程处理,要么新建一个线程处理。那我们怎么确定线程池创建过多少个线程呢?可以加一段代码打印出来。

如下:

  1. private static int counter = 0; 
  2.  
  3. public static void main(String[] args) { 
  4. //使用线程池 
  5. ThreadPoolExecutor threadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool(); 
  6. //提交2000个任务 
  7. for(int i = 0; i < 2000; i++) { 
  8. threadPool.submit(new Add()); 
  9. threadPool.shutdown(); 
  10. //打印最多使用线程数 
  11. System.out.println("largestPoolSize:" + threadPool.getLargestPoolSize()); 
  12. System.out.println(counter); 

输出结果如下:

  1. 第一次: 
  2. largestPoolSize:11 
  3. 1937 
  4. 第一次: 
  5. largestPoolSize:14 
  6. 1956 
  7. 第一次: 
  8. largestPoolSize:31 
  9. 1970 

可以看到每次都不一样,线程池之前有文章讲过,这个系列后面还会深入讲解。

关于 largestPoolSize, 注释说明了,记录线程池中最大的线程数。

  1. /** 
  2. * Tracks largest attained pool size. Accessed only under 
  3. * mainLock. 
  4. */ 
  5. private int largestPoolSize; 

面试官:看你代码中写了调用线程池的shutdown,那shutdown 和 shutdownNow 方法什么区别?

安琪拉:shutdown 是将线程池的状态设置为SHUTWDOWN状态,正在执行的任务会继续执行下去,没有被执行的则中断。而shutdownNow则是将线程池的状态设置为STOP,正在执行的任务则被停止,没被执行任务的则返回。

源码对比:

  1. //shutdown 
  2. public void shutdown() { 
  3.   final ReentrantLock mainLock = this.mainLock; 
  4.   mainLock.lock(); 
  5.   try { 
  6.     checkShutdownAccess(); 
  7.     //设置线程池状态为SHUTDOWN 
  8.     advanceRunState(SHUTDOWN); 
  9.     interruptIdleWorkers(); 
  10.     onShutdown(); // hook for ScheduledThreadPoolExecutor 
  11.   } finally { 
  12.     mainLock.unlock(); 
  13.   } 
  14.   tryTerminate(); 
  1. public List<Runnable> shutdownNow() { 
  2.   List<Runnable> tasks; 
  3.   final ReentrantLock mainLock = this.mainLock; 
  4.   mainLock.lock(); 
  5.   try { 
  6.     checkShutdownAccess(); 
  7.     //设置线程池状态为STOP 
  8.     advanceRunState(STOP); 
  9.     interruptWorkers(); 
  10.     //把队列剩余等待执行任务取出,返回 
  11.     tasks = drainQueue(); 
  12.   } finally { 
  13.     mainLock.unlock(); 
  14.   } 
  15.   tryTerminate(); 
  16.   return tasks; 

面试官:线程池有哪几种状态?

安琪拉:5种,注意这里说的是线程池的状态,不是线程的状态。下面是线程池的状态流转图:

本文是《并发》系列第一集,主要介绍了一些并发、并行、高并发的一些基础概念,以及并发安全问题的案例,下一集讲并发的风险与优势和CPU多级缓存,以及一些内存操作的指令,然后说Java内存模型。

完整大纲参考:

 

责任编辑:武晓燕 来源: 安琪拉的博客
相关推荐

2021-12-08 15:09:26

鸿蒙HarmonyOS应用

2021-06-11 07:30:30

并发高并发内存

2013-04-25 14:05:20

Windows PhoWindows PhoWindows Pho

2021-07-03 17:44:34

并发高并发原子性

2023-09-13 06:49:05

克隆内测版Illmind

2020-08-18 13:50:04

Tomcat高并发Java

2019-06-28 10:55:04

预热高并发并发高

2021-02-26 13:08:27

Java高并发AQS

2020-09-03 14:30:40

Tomcat 拆解调优

2023-07-06 08:06:47

LockCondition公平锁

2021-04-28 08:52:22

高并发架构设高并发系统

2021-04-28 08:00:16

多线程高并发操作

2019-09-16 09:23:34

高并发编程CountDownLaCyclicBarri

2013-07-17 17:03:23

Ngx_luaNginx

2023-12-08 18:01:25

Java关键字

2009-11-25 11:20:31

并发

2014-12-25 09:47:59

GuavaGuava并发

2021-07-30 07:28:15

WorkerPoolGo语言

2011-11-30 12:32:38

企业防毒防毒方案拯救三

2023-08-01 09:00:00

高并发性能优化
点赞
收藏

51CTO技术栈公众号