一.有锁并发
synchronize JVM提供的锁, 他影响的范围是跟CPU有关,会造成阻塞现象,阻塞现场会造成线程上下文的切换线程间切换,实际上是作了很多事情的。
比如:当前线程需要执行的指令,以及他的程序计数器,内部的一些数据是需要保存的。
二.无锁并发
CAS是一种策略,这个策略是为了保证 主内存中的数据在被多个线程赋值的使用,是一个准确的。
为了达到这个目的,他采取的方案是:把旧值保留,拿就只与主内存比对,如果不对,重读在加载。
CAS 必须和volatile关键字配合使用,原因: 在保证线程安全性的前提下,要保证可见性。
运用CAS的理论能完成锁功能一样的效果,加锁了吗?没加, 一直在做值检测, 过了就放掉。
synchronize--》休息,阻塞掉,直接放掉CPU资源,下一次过来用的使用,需要恢复寄存器数据。
三.CAS这种无锁模式去进行安全处理会比有锁快吗?
1.你的线程数量不多,最好和CPU核数对应,他是最优的方案。
2.如果我上来100000线程CAS效率绝对慢。
结合CAS与volatile实现无锁并发情况的适用场景:
多核CPU场景下,且线程数少
CAS基于乐观锁思想,最乐观结果,不怕别的线程来修改共享变量,改了也没事,我在重试
synchronize基于悲观锁思想:最悲观结果,得放着其他线程来修改共享变量,我上锁,你们都别改,我改了解开你们才有机会
CAS体现的是无锁并发,无阻塞并发
因为没有synchronized,线程不会陷入阻塞,这是效率提升的因素之一
如果竞争几率,重试必然发生频繁,效率会下降
最好结果为线程数不超过CPU核心数。
四.原子变量
本质上是一组工具,位置在atomic包下,实际上祖师爷已经帮我们把CAS的相关实现全部搞定,并且封装了。
处理并发安全问题上:
1.单个原子处理
2.块处理
本质上分类两类:
1.保证基本数据类型的原子性(AtomicInteger...)
2.保证引用类型的原子性(AtomicReference)
所有的原子操作实际上是在底层进行了一个CAS的循环比较,只有达成目的,我就退出。
祖师爷在考虑并发的时候,考虑了我们在实际并发中会出现的问题场景,其中绝大部分是对于某个属性的数据安全的处理,所以他帮我搞了一组Atomic实现,利用的是CAS理论,不加锁的方案进行处理,在线程数不多的情况下,核数合适的情况下,当前方案的速度优于synchronize。
五.ABA问题
在多线程对于原子变量操作时,会发生将数据变更回去的现象,CAS在判断时会造成概念上的认知错误,但是实际上对业务结果是不变的.
但是实际业务运用过程中可能会需要知道整个运行过程值是否改变:
1.通过AtomicStampedReference 追溯版本号。
2.通过AtomicMarkableReference 得到是否更改结果。
六.不同场景下的原子变量操作方案-- AtomicReferenceFieldUpdater
AtomicReference本质上是对于引用类型的地址
但是我们常规使用中,更多的业务是要判定内部数据是否一致
1.原子数组:保证数组内元素的线程安全
2.字段:字段更新器 AtomicReferenceFieldUpdater
3.累加业务:原子累加器
七.不同场景下的原子变量操作方案-- LongAdder
输出结果:
性能提升的原因很简单,就是有竞争时,设置多个累加单元,然后最后结果汇总,他这样的累加操作不同的cell变量,因此减少了Cas重试失败,从而提高性能。
八.LongAdder原理分析
性能提升的原因很简单,就是有竞争时,设置多个累加单元,然后最后结果汇总,他这样的累加操作不同的cell变量,因此减少了Cas重试失败,从而提高性能。
LongAdder伪共享原理与缓存行:
什么是伪共享?
CPU高度缓冲器的存储体系下,一个基本的缓存单位叫做缓存行,一个缓存行的大小为64byte,
数组是一块连续的空间,因为副本数据的原因,数组加载到缓存当中,数据超过64字节会占用多行,若小于64字节则占用一行。