Java并发编程
目录
synchronized关键字的底层原理(基础)以及跟lock锁之间的区别?
ConcurrentHashMap实现线程安全的底层原理是什么?
BAT面试官为什么都喜欢问并发编程的问题?
synchronized实现原理、CAS无锁化的原理、AQS是什么、Lock锁、ConcurrentHashMap的分段加锁的原理、线程池的原理、java内存模型、volatile说一下吗、对java并发包有什么了解?一连串的问题
写一些java web系统,运用一些框架和一些第三方技术,写一些类似于crud的业务逻辑,把各种技术整合一下,写一些crud而已,没什么技术含量。很多人可能写好几年的代码,都不会用到多少java并发包下面的东西
如果说你要面试一些稍微好一点的公司,技术稍微好一点,你只要去做一个技术含量稍微高一点的系统,并发包下面的东西还是很容易会用到的。尤其是BAT,中大厂,有一定规模的公司,做出来的系统还是有一定的技术含量的
synchronized关键字的底层原理(基础)以及跟lock锁之间的区别?
之前有一些同学去一线互联网大厂里去面试,聊并发编程这块的内容,问的比较深一点,就说synchronized的底层原理是什么呢?他当时就答不出来了
如果我要是对synchronized往深了讲,他是可以很深很深的,内存屏障的一些东西,cpu之类的硬件级别的原理,原子性、可见性、有序性,指令重排,JDK对他实现了一些优化,偏向锁,几个小时
面试突击第三季,快速过一下常见的高频面试题而已
其实synchronized底层的原理,是跟jvm指令和monitor有关系的
你如果用到了synchronized关键字,在底层编译后的jvm指令中,会有monitorenter和monitorexit两个指令
monitorenter
// 代码对应的指令
monitorexit
那么monitorenter指令执行的时候会干什么呢?
每个对象都有一个关联的monitor,比如一个对象实例就有一个monitor,一个类的Class对象也有一个monitor,如果要对这个对象加锁,那么必须获取这个对象关联的monitor的lock锁
他里面的原理和思路大概是这样的,monitor里面有一个计数器,从0开始的。如果一个线程要获取monitor的锁,就看看他的计数器是不是0,如果是0的话,那么说明没人获取锁,他就可以获取锁了,然后对计数器加1
这个monitor的锁是支持重入加锁的
加锁,一般来说都是必须对一个对象进行加锁
如果一个线程第一次synchronized那里,获取到了myObject对象的monitor的锁,计数器加1,然后第二次synchronized那里,会再次获取myObject对象的monitor的锁,这个就是重入加锁了,然后计数器会再次加1,变成2
这个时候,其他的线程在第一次synchronized那里,会发现说myObject对象的monitor锁的计数器是大于0的,意味着被别人加锁了,然后此时线程就会进入block阻塞状态,什么都干不了,就是等着获取锁
接着如果出了synchronized修饰的代码片段的范围,就会有一个monitorexit的指令,在底层。此时获取锁的线程就会对那个对象的monitor的计数器减1,如果有多次重入加锁就会对应多次减1,直到最后,计数器是0
然后后面block住阻塞的线程,会再次尝试获取锁,但是只有一个线程可以获取到锁
synchronized和lock的区别
聊聊你对CAS的理解以及底层实现原理?
Conmpare And Swap(比较和交换)
首先说一说CAS能解决的问题。我们都知道当多个线程对同一个数据进行操作的时候,如果没有同步就会产生线程安全问题。为了解决线程线程安全问题,我们需要加上同步代码块,操作,如加上synchronized。但是某些情况下这并不是最优选择。
synchronized关键字会让没有得到锁资源的线程进入BLOCKED状态,而后在争夺到锁资源后恢复为RUNNABLE状态,这个过程中涉及到操作系统用户模式和内核模式的转换,代价比较高。这个过程是一个串行的过程,效率很低。
尽管JAVA 1.6为synchronized做了优化,增加了从偏向锁到轻量级锁再到重量级锁的过过度,但是在最终转变为重量级锁之后,性能仍然比较低。所以面对这种情况,我们就可以使用java中的“原子操作类”。
而原子操作类的底层正是用到了“CAS机制”。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。(具体实现详细的见https://blog.****.net/qq_32998153/article/details/79529704)
从思想上来说,synchronized属于悲观锁,悲观的认为程序中的并发情况严重,所以严防死守,CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去重试更新。
说了这么多,CAS是否是完美的呢,答案也是否定的。下面是说一说CAS的缺点:
1) CPU开销过大
在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很到的压力。
2) 不能保证代码块的原子性
CAS机制所保证的知识一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized了。
3) ABA问题
这是CAS机制最大的问题所在。
ConcurrentHashMap实现线程安全的底层原理是什么?
在JDK 1.7 版本,它的实现方式是分段加锁,将HashMap在底层的数组分段成几个小数组,然后给每个数组分别加锁。
JDK1.8以及之后,做了一些优化和改进,锁粒度的细化。
仍然是一个大的数组,数组中每个元素进行put都是有一个不同的锁,刚开始进行put的时候,如果两个线程都是在数组[5]这个位置进行put,这个时候,对数组[5]这个位置进行put的时候,采取的是CAS策略。
同一时间,只有一个线程能成功执行CAS,其他线程都会失败。
这就实现了分段加锁的第一步,如果很多个线程对数组中不同位置的元素进行操作,大家是互相不会影响的。
如果多个线程对同一个位置进行操作,CAS失败的线程,就会在这个位置基于链表+红黑树来进行处理,synchronized([5]),进行加锁。
综上所述,JDK1.8之后,只有对相同位置的元素操作,才会加锁实行串行化操作,对不同位置进行操作是并发执行的。