Android JUC03 --- CAS与原子变量

一.有锁并发

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)

AtomicInteger例子

所有的原子操作实际上是在底层进行了一个CAS的循环比较,只有达成目的,我就退出。

祖师爷在考虑并发的时候,考虑了我们在实际并发中会出现的问题场景,其中绝大部分是对于某个属性的数据安全的处理,所以他帮我搞了一组Atomic实现,利用的是CAS理论,不加锁的方案进行处理,在线程数不多的情况下,核数合适的情况下,当前方案的速度优于synchronize。

五.ABA问题

在多线程对于原子变量操作时,会发生将数据变更回去的现象,CAS在判断时会造成概念上的认知错误,但是实际上对业务结果是不变的.

但是实际业务运用过程中可能会需要知道整个运行过程值是否改变:

1.通过AtomicStampedReference  追溯版本号。

2.通过AtomicMarkableReference 得到是否更改结果。

六.不同场景下的原子变量操作方案-- AtomicReferenceFieldUpdater

AtomicReference本质上是对于引用类型的地址

但是我们常规使用中,更多的业务是要判定内部数据是否一致

1.原子数组:保证数组内元素的线程安全

2.字段:字段更新器  AtomicReferenceFieldUpdater

3.累加业务:原子累加器

字段更新器

七.不同场景下的原子变量操作方案-- LongAdder

LongAdder与Atomic比较1
LongAdder与Atomic比较2

输出结果:

输出结果

性能提升的原因很简单,就是有竞争时,设置多个累加单元,然后最后结果汇总,他这样的累加操作不同的cell变量,因此减少了Cas重试失败,从而提高性能。

八.LongAdder原理分析

性能提升的原因很简单,就是有竞争时,设置多个累加单元,然后最后结果汇总,他这样的累加操作不同的cell变量,因此减少了Cas重试失败,从而提高性能。

LongAdder原理分析

LongAdder伪共享原理与缓存行:

什么是伪共享?

CPU高度缓冲器的存储体系下,一个基本的缓存单位叫做缓存行,一个缓存行的大小为64byte,

数组是一块连续的空间,因为副本数据的原因,数组加载到缓存当中,数据超过64字节会占用多行,若小于64字节则占用一行。

你可能感兴趣的:(Android JUC03 --- CAS与原子变量)