彻底学会Java并发编程——共享模型之无锁(CAS和volatile)

68 阅读3分钟

加锁保护共享资源

无锁保护共享资源

用的是CAS的思想。

AtomicInteger不用加锁就能保护共享变量的线程安全。

关键是compareAndSet(CAS)配合while(true)来使用

上图是CAS工作的流程。

CAS代码分析:

比较value和prev的值,如果一样就输出next,如果不一致,就把prev改成value的值然后接着循环。value是别的线程修改后的balance的值。

CAS的效率更高,比synchronized高。原因是无锁情况下就算重试失败,线程仍然在高速运行。但synchronized会让线程在没有锁的情况下发生阻塞,进入上下文切换。

CAS在多核cpu能发挥优势,如果没有cpu,线程就分不到时间片仍然会进入可运行状态导致上下文切换。

特点

CAS是乐观锁的思想:具体为不阻止别的线程使用共享资源,改了的话重试即可。

synchronized是悲观锁的思想:具体为给共享资源上锁,不给别的线程用。

CAS线程不会阻塞,但如果竞争激烈就会频繁发生重试,效率降低。

原子整数(AtomicInteger)

一些方法

getandAdd等于上面的实现。

上面是做原子性乘法运算。

自己写原子性乘法。

原子引用

和原子整数使用方法类似

用原子引用来实现逻辑 BigDecimal类型使用原子引用。

AtomicReference

这种情况下主线程无法感知到其他线程对共享变量的修改,因为他查的只是共享变量的最新值。

AtomicStampReference

通过获取版本号可以改变这种情况。加一个stamp版本号,需要看版本号便没变化。

追踪原子引用整个的变化过程。

AtomicMarkableReference

不关心改了几次,只关心改没改过。

原子数组

保护的是数组里的元素

创建原子数组。

原子更新器

保护对象赋值的一个原子性:

原子累加器

supplier:提供累加器对象,有返回结果。()->结果

consumer:有参数但是没有返回结果。执行累加操作。

比较了两种原子累加器:AtomicLong和LongAdder,专门做累加的。

LongAdder性能更高。

性能提升是因为在有竞争的时候设置了多个累加单元,在多个共享变量 Cell()上进行累加,重试次数减少,最后再把结果汇总。

LongAdder的原理

用CAS实现锁

0表示加锁,1表示没加锁。

测试代码如下:

Cell共享单元实现逻辑:

缓存行伪共享

通过上面的@sun.misc.Contended来实现

cell是数组形式,一个cell是24字节(16字节的对象头和8字节的value)

原理是使用该注释的对象或字段的前后各增加128字节大小的空白,让cpu把对象读到缓存的时候占用不同的缓存行

这样当cpu core1要修改cell0,而cpu core2要修改cell1的时候,就不会造成对方的缓存行失效。

一个缓存行加了多个cell对象叫伪共享。

cpu和内存的运行效率差距大,所以需要预读数据到缓存来提升效率。

缓存由缓存行组成,每个缓存行对应一段内存大约是64byte

如果某个cpu核更改了数据,那么该数据在其他cpu核对应的缓存行失效。

LongAdder类里add方法的实现(源码):

LongAccumulate方法实现(源码)

sum方法的实现(源码)

unsafe对象

原子整数,原子引用,原子数组,原子更新器,原子累加器,底层都是unsafe,基于unsafe类来实现的。

获取unsafe实例对象。

给teacher赋值

上面是unsafe对象cas基本应用

自己实现原子整数类: