针对上期Java高并发【你问我答】中读者提出的问题,王锐同学的回答如下。
一
美团内部使用过Akka么?有什么坑?
——Absurd
“
答:
只简单用过Akka,Akka cluster没用过。
1. actor里面最好不要有阻塞操作,如果有的话一定要设置下dispatcher。 2. 监控好邮箱队列长度,最简单的做法就是原子变量记录。 3. 如果一直创建新actor,会有内存泄露;必要时可缓存,或者调用stop方法。 目前我们系统应用了Akka,系统比较稳定。
”
二
在分布式、微服务的大环境下,Java并发还有学习的必要吗?不都是Redis以及ZooKeeper的分布式锁吗?也都是集群部署的形式。
——james.yue
“
答:
并发编程所包含的不仅仅是锁,其实包含了4层含义:
① 内存模型和一致性协议;
② 锁相关包括自旋锁、死锁、活锁等理论;
③ 无锁的数据结构;
④ 各个语言的并发模型。
一般的并发基础都是基于单机的,但是基础理论的强大之处在于它可以影响你在系统设计时候的取舍,比如分布式锁也需要考虑是否重入、阻塞中断、超时唤醒、失效取消等等问题,而不是简单的使用ZooKeeper或者Redis就能完美解决的。
计算机世界没有银弹。
”
三
ReentrantLock和Synchronized之间怎么选择?
——我木有名字
“
答:
1.6以前Synchronized实现稍显笨重,所以如果你是1.6以前的JDK并且是中等程度的并发,请使用ReentrantLock。如果并发很高,ReentrantLock的CAS机制会带来一定的CPU浪费,那么Synchronized更好。1.6以后,Synchronized做了大量的优化,所以在没有特殊功能要求的情况下(比如你需要中断锁或者tryLock或者读写锁或者公平锁等)推荐使用Synchronized,特殊需求使用ReentrantLock这个一系列的工具。
”
四
队列的拒绝机制。
——落拓书生
“
答:
因为不确定是哪种队列,所以没办法说的很细,一般的队列拒绝策略有以下几种:拒绝最新的,就是最后请求加入队列的直接丢弃;拒绝最老的,就是把队列头部的拒绝;新请求加入的可以入队;随机拒绝,就是兼容上面二者。
”
五
请详细介绍一下Java 8中ConcurrentHashMap的原理。
——不告诉你
“
答:
这个问题太大了,说一些关键点吧,网上也有很多的源码分析,就不赘述了:
① 摒弃了Segment(锁段)的概念,而是启用了一种全新的方式实现,利用CAS算法。因为原有的分段锁在扩容以及并发度调整的时候会同时锁住多个锁(每个段一个锁),这样容易造成死锁以及极大的性能损耗。
② Hash算法优化,这个和HashMap是一致的,为了解决在key冲突多的时候退化成链表的问题。
③ 大量应用CAS方法进行变量、属性的修改工作。利用CAS进行无锁操作,可以大大提高性能。
”
六
实际工作当中不太接触并发的知识,但是面试的时候问的最多的是并发,如何破解?
——凌亂
“
答:
面试过程中更多是对知识的考察,并没有太多深入的实际使用,所以只需要去学习就OK了。
学习并发有4个方面:
① 内存模型和一致性协议
② 锁相关包括自旋锁、死锁、活锁等理论
③ 无锁的数据结构
④ 各个语言的并发模型(如果是Java就是Java的并发模型)。
如果真的是这个职位需要深入的并发理论,认个怂也没啥。因为你不具备这个经验和基础去做这样的工作很明显不匹配,还不如回过头来静下心好好学习。
”
七
对于萌新的我来说,就是想让职场的专家提一些学习Java的意见,谢谢。
——小小小小小小小小小旺旺
“
答:
学习Java就一个思路,看书+多写代码。
① 学习Java基础,《Core Java》这本书很不错,《Effective Java》也是必读,这一步有什么坑的话就是,不要看《Thinking in Java》。
② 学习J2EE相关的知识。
③ 学习一些常用的开源软件。
④ 平时多看各大公司的技术公众号(比如美团点评技术团队,这里是小编强加的硬广,么么哒)。
另外个人的经验的话,在入门阶段应该以看书为主建立整体的知识体系。在提升阶段可以多看一些有质量的博客或者公众号。
”
八
读写锁和condition一般在什么场景下面使用?ConcurrentHashMap在Java 7和Java 8中差了几倍的代码量,后者看起来好复杂,能不能指点一下?
——不以物喜
“
答:
① 读写锁一般在读多写少的场景下使用。
② condition主要是用来做线程协调的,如果你用的Synchronized那么需要wait和notify。如果你用了ReentrantLock那么就需要condition来完成wait和notify的功能了。
③这个可以看上面第5题哈。
”
九
线程池ThreadPoolExecutor的内部机制是怎样的?
——码农一枚
“
答:
整体处理流程:
① 当一个任务被提交到线程池时,首先查看线程池的核心线程是否都在执行任务,否就选择一条线程执行任务,是就执行第二步。
② 查看核心线程池是否已满 , 不满就创建一条线程执行任务,否则执行第三步。
③ 查看任务队列是否已满,不满就将任务存储在任务队列中,否则执行第四步。
④ 查看线程池是否已满,不满就创建一条线程执行任务,否则就按照策略处理无法执行的任务。
在ThreadPoolExecutor中表现为:
① 如果当前运行的线程数小于corePoolSize,那么就创建线程来执行任务(执行时需要获取全局锁)。
② 如果运行的线程大于或等于corePoolSize,那么就把task加入BlockQueue。
③ 如果创建的线程数量大于了BlockQueue的最大容量, 那么创建新线程来执行该任务。
④ 如果创建线程导致当前运行的线程数超过maximumPoolSize,就根据饱和策略来拒绝该任务。
创建线程池:
① 首先要提供maxCorePoolSize,maxinumPoolSize,keepAliveTime,BlockQueue这几个线程运行所需的必要参数。
② 可选参数:饱和策略(当任务无法被执行时的处理方式)。
① ThreadPoolExecutor.AbortPolicy,用于被拒绝任务的处理程序,它将抛出RejectedExecutionException。
② ThreadPoolExecutor.CallerRunsPolicy用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务。
③ DiscardOldestPolicy用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试 execute。
④ DiscardPolicy用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。
提交任务:
① 无返回值的任务使用execute(Runnable)
② 有返回值的任务使用submit(Runnable)
关闭线程池:
调用shutdown或者shutdownNow,两者都不会接受新的任务,而且通过调用要停止线程的interrupt方法来中断线程,有可能线程永远不会被中断,不同之处在于shutdownNow会首先将线程池的状态设置为STOP,然后尝试停止所有线程(有可能导致部分任务没有执行完)然后返回未执行任务的列表。而shutdown则只是将线程池的状态设置为shutdown,然后中断所有没有执行任务的线程,并将剩余的任务执行完。
线程池的配置:
① 如果是CPU密集型任务,那么线程池的线程个数应该尽量少一些,一般为CPU的个数+1条线程。
② 如果是IO密集型任务,那么线程池的线程可以放的很大,如2*CPU的个数。
③ 混合型任务的话,如果可以拆分的话,可以通过拆分成CPU密集型和IO密集型两种来提高执行效率;如果不能拆分的的话就可以根据实际情况来调整线程池中线程的个数。
监控线程池的状态:
① taskCount:线程需要执行的任务个数。
② completedTaskCount:线程池在运行过程中已完成的任务数。
③ largestPoolSize:线程池曾经创建过的最大线程数量。
④ getPoolSize获取当前线程池的线程数量。
⑤ getActiveCount:获取活动的线程的数量
然后通过继承线程池,重写beforeExecute,afterExecute和terminated方法来在线程执行任务前,线程执行任务结束,和线程终结前获取线程的运行情况,根据具体情况调整线程池的线程数量。
”
十
ReentrantLock中condition的实现原理是怎么样的?和AQS的关系?
——码农一枚
“
答:
关于ReentrantLock中condition的实现原理网上有很多源码分析了。这里说下AQS这个东东。
在并发的世界中,有一件顶重要的事情就是执行流的协调,在Java世界中就是线程间的协调和通信,基本包括这几种:原子性管理、线程的阻塞和解除阻塞以及排队,这些功能统称同步器。
同步器的核心功能有很多,比如:内部同步状态的管理(例如:表示一个锁的状态是获取还是释放),同步状态的更新和检查操作,且至少有一个方法会导致调用线程在同步状态被获取时阻塞,以及在其他线程改变这个同步状态时解除线程的阻塞等等。
实现同步器可以有集中方法:互斥排它锁的不同形式、读写锁、信号量、屏障、Future、事件指示器以及传送队列等。每一种同步器都可以实现其他类型的同步器。但是从设计层面上讲这样做带来的复杂性、开销、不灵活等问题。所以,Java对同步器做了一个统一的抽象为构造同步器做了一个通用机制就是AQS。AQS也是J.U.C包的基石,许多功能比如锁,比如信号量等等都是基于AQS的。也就是说ReentrantLock中的condition也是基于AQS实现的。
”
十一
请问怎么才能有效的阅读源码?阅读过程分哪几步?
——小豆豆
“
一般都是带着实际问题去阅读更好,阅读源码一般的过程是:
① 熟悉文档;
② 熟悉基本流程;
③ DEBUG看源码。
”
十二
我在看你们之前一篇文章“Java内存访问重排序的研究”,将你们给的一个demo写了一下,但是我去掉了private volatile SomeThing object;的volatile关键字,然后测试怎么测试都不会出现new对象的时候内部重排序导致的对象创建不完整问题,这是什么原因呢?
——小林家的...
“
重排序是一个比较底层的操作,受好多东西影响,其中CPU是最主要的原因。我们的PC机一般都是Intel的处理器,Intel处理器有着比较复杂的重排序规则,所以Intel在很多情况下都不需要volatile关键字。你去掉这个关键字,是不是也存在着不加volatile关键字也可以的场景?
”
十三
项目前端用户秒杀,没有用户概念,请问后端有哪些手动防止超卖,减少后端压力。
——小小纸飞机
“
这个问题实在没看懂想问什么。
”