详解volatile关键字和原子引用
本篇看一下Volatile关键字和原子引用。
上图就是JUC包结构,总共分成三块
(1)java.util.concurrent:并发包基础类,包括阻塞队列,线程池相关类,线程安全Map等。
(2)java.util.concurrent.atomic:原子引用相关类
(3)java.util.concurrent.locks:线程锁相关类
线程池技术在之前的讲解应该很清楚了,今天主要解析一个volatile关键字以及原子引用的相关类。这一篇文件涉及到JMM(java内存模型)的知识,之前也有讲解。
一、volatile
这个关键字,是JVM提供的轻量级的同步机制。可能在业务功能开发中很少涉及,但其是JUC包里面拥有重要地位的大哥。
首先看下理论:
(1)保证变量内存可见性:就是当前线程对某个volatile修饰的变量进行操作的话会及时通知到其它线程。比如现在的在线编辑文档,当某个人编辑了文档并保存之后,其他人会立马收到文档已被修改的通知。
(2)不保证原子性: 原子性的意思就是某个元素已经是最小的了,不可在被分割,比如现实中原子就是最小的粒子了,不可能再被分割。而映射到这边就是说某个操作不可在被分割成两个或多个操作,要么成功,要么失败。
(3)禁止指令重排:JVM为了获取最快的运行速度,会在底层一些没有先后性要求的指令进行一些指令重排的操作,在代码层面是一步一步执行,但在底层执行的时候并不是如此。比如JVM创建了一个对象有三步,创建实例对象、分配空间、将引用指向内存空间,但是第一步和第二步并没有什么关系,这个时候就可能会发生指令重排的操作。
代码示例:
(1)可见性代码验证:
当资源类变量a未加volatile关键字修饰的时候
当添加了volatile关键字修饰后:
(2)不保证原子性验证:
如果保证原子性,那么最后输出的结果应该是20000,而真实输出是19734,所以这可以 证明其不保证原子性。
JVM为了提供了可保证原子性的原子类。
二、原子类
下图就是原子类的结构图
先用AtomicInteger类是否可以保证原子性。不多说,上图片。
可以看到,用原子类AtomicInteger类是可以获取期望值的,也就是保证了原子性。
接下来看一下原子类底层是怎么样的。
上图可以看到,有三个变量,分别是value,valueOffset,unsafe。
(1)value:是volatile关键字修饰的,也就是说原子类底层也是volatile变量。
(2)valueOffset:当前变量的内存位移值。
(3)unsafe:下图可以看到,unsafe类里面都是native修饰的本地方法,是直接调用其他语言进行操作变量的。
看一下原子类的addAndGet方法为什么能实现原子性呢?
点进去会看到它调用了unsafe的getAndAddInt方法。
继续点下去会看到如下方法,里面有个compareAndSwap。
介绍一个compareAndSwap方法,简称CAS(比较并交换),其有三个值,内存值V,旧的预估值A,更新值B。只有当V和A相等的时候,才会将变量的的值修改为B,否则什么都不干。
可以看到,上述的getAndAddInt方法是个自旋锁的实现,一直调用getIntVolatile(也是本地方法)方法获取对象的最新值,获取之后就会调用CAS方法。
CAS:原称为Compare-And-Swap,比较并交换,是一条cpu的并发原语,它的功能是判断内存某个位置的值是否为预期值,如果是则更改为预期值,这个过程是原子的。
并发原语体现在调用unsafe的方法。原语是属于操作系统的范畴,原语的执行必须是联系的,也就是原子性,不会造成所谓的数据不一致的问题。(这个我也不是很懂,毕竟我不是科班出身的程序员,应该是要学操作系统才会明白)
(如果大厂面试的时候让你手写一个自旋锁,就把上述代码糊他脸上)
上述就是其保证原子性的方法,利用自旋锁和unsafe类。
说完原子类的底层,进行拓展一下:上述可以看到,原子类java只提供了基本类型的封装,例如AtomicInteger,AtomicLong等,但是工作中需要很多其他自定义的实体,也要保证原子性,该怎么办呢?
别慌,JVM还提供了原子引用类型AtomicReference,下图可见此类是一个泛型类。
下次会带大家看一下CAS导致的ABA问题。
=======================================================
我是Liusy,一个喜欢健身的程序员。
欢迎关注公众号【Liusy01】,一起交流Java技术及健身,获取更多干货。