本文转载自微信公众号“Angela的博客”,作者:Angela。转载本文请联系Angela博客公众号。面试官:根据你的简历,你目前正在写一篇关于并发编程的博客,对吧?Angela:闲来无事,看看闲书,写些轶事。承蒙各位读者的厚爱,我有了这个打算。面试官:别拖我了,“我没事做”?阿里不是用996吗?Angela:修福宝,你知道吗?……技术人的一天能算996吗?专访官:算了,说正题吧。首先,告诉我什么是并发?Angela:并发是指存在两个或多个线程,这些线程同时对同一台物理机中的资源进行操作。面试官:那么并发和并行的区别是什么?Angela:举个生活中的例子:你在玩王者荣耀,这时候你女朋友在找你的视频,你玩王者荣耀才回复。您不支持并发(也不支持并行);你在玩王者荣耀,这时候你女朋友给你发微信,你退出王者荣耀,回到微信后又回到王者荣耀,在微信和王者荣耀之间来回切换代表你支持它并发,但不支持并行性;如果你在玩王者荣耀,这个时候你女朋友给你打电话,你一边玩荣耀一边接电话,说明你支持并行。并行性的关键点是物理上的“同时性”。当我们在单核CPU上时,我们既可以写代码,也可以听音乐。这种多线程实际上是基于操作系统根据CPU时间片进行任务轮换,是一种伪“同时”,只能说是并发,不能说是并行,但是多核CPU可以支持每个核心同时运行任务,真正的“同时”并行。Erlang之父JoeArmstrong画了一张图来解释并发和并行的区别,Concurrent(并发),Parallel(并行)。并发允许两组孩子轮流使用咖啡机。并联就是同时有两台咖啡机,两队小朋友同时使用,不冲突。面试官:高并发呢?你知道高并发吗?Angela:【心想,是时候来造火箭了】你说的HighConcurrency(高并发)对吧?.通常我们在谈论并发的时候,更多的是关注线程安全,但是在讨论高并发的时候,关注的焦点不仅仅是线程安全,而是如何在短时间内处理大量的请求来保证系统的响应时间和吞吐量是否可靠,更多关注的是稳定性问题(SRE),高并发涉及完整的系统知识,线程安全只是其中的一小部分。高并发是当前互联网设计系统需要考虑的重要因素之一。一般来说,就是通过严谨的设计保证系统可以并行处理很多请求。这就是我们常说的“高并发”。也就是说,系统在一定时间内可以提供很多请求,但不会影响系统的性能。如果要设计一个高可用、高性能的系统,需要考虑很多方面,比如硬件、软件、编程语言的选择、网络的考虑、整体系统架构、数据结构、算法优化、数据库优化等诸多方面。这其中的每一点都需要大量的知识去展开,Angela会在后续的课程中更新这部分内容。面试官:说说你们系统的QPS?Angela:大促场景QPS可以10万+,日常业务高峰期2万+,其他时间几千。事实上,对于大多数系统来说,几十个或几百个都是正常的。如果QPS超过1000,那就不低了。有些企业会有高峰。实际中QPS稳定在10000以上的系统并不多,可以日常关注一下。自己系统的QPS,这个问题在面试中经常被问到。面试官:一般我们有什么工具来模拟并发请求?Angela:PostMan、ApacheBench(AB)、Jmeter,推荐使用Jmeter。面试官:那你能写一段代码来演示一下并发安全的问题吗?安吉拉:是的。把笔递给我,顺便把A4纸给我记下来。publicclassConcurrencySafeTest{privatestaticintcounter=0;publicstaticvoidmain(String[]args){//使用线程池ThreadPoolExecutorthreadPool=(ThreadPoolExecutor)Executors.newCachedThreadPool();//提交2000个任务for(inti=0;i<2000;i++){threadPool.submit(newAdd());}threadPool.shutdown();System.out.println(counter);}staticclassAddimplementsRunnable{@Overridepublicvoidrun(){counter++;}}}我们进行计数操作,执行2000次,期待执行result应该是2000,但是实际执行结果如下:19711987因为?系列是从基础开始的,所以上面的代码部分涉及到了一些后面的内容,比如线程池和线程的使用,这里只需要对并发的安全有一个大概的了解后面会细说这个问题,面试官的问题作为延伸阅读。面试官:我看到你在代码中使用了CachedThreadPool。CachedThreadPool线程池为2000次任务执行创建了多少个线程?安吉拉:答案是不确定的。CachedThreadPool缓存线程(多路复用线程),不对任务进行排队,来一个任务,要么复用已有的线程处理,要么新建线程处理。那么我们如何判断线程池已经创建了多少个线程呢?您可以添加一段代码以将其打印出来。如下:privatestaticintcounter=0;publicstaticvoidmain(String[]args){//使用线程池ThreadPoolExecutorthreadPool=(ThreadPoolExecutor)Executors.newCachedThreadPool();//提交2000个任务for(inti=0;i<2000;i++){threadPool.submit(newAdd());}threadPool.shutdown();//打印最大使用线程数System.out.println("largestPoolSize:"+threadPool.getLargestPoolSize());System.out.println(counter);}输出结果如下:第一次:largestPoolSize:111937第一次:largestPoolSize:141956第一次:largestPoolSize:311970可以看到每次都不一样。线程池之前有一篇文章提到过,这个系列后面会深入讲解。关于largestPoolSize,注释上说它记录的是线程池的最大线程数。/***Trackslargestattainedpoolsize.Accessedonlyunder*mainLock.*/privateintlargestPoolSize;面试官:看到你代码里写了调用线程池的shutdown,请问shutdown和shutdownNow有什么区别?Angela:关机就是将线程池的状态设置为SHUTWDOWN状态,正在执行的任务会继续执行,没有执行的任务会被中断。而shutdownNow就是设置线程池的状态为STOP,正在执行的任务停止,没有执行的任务返回。源码对比://shutdownpublicvoidshutdown(){finalReentrantLockmainLock=this.mainLock;mainLock.lock();try{checkShutdownAccess();//设置线程池状态为SHUTDOWNadvanceRunState(SHUTDOWN);interruptIdleWorkers();onShutdown();//hookforScheduledThreadPoolExecutor}finally{mainLock.unlock();}tryTerminate();}publicList
