volatile是java虚拟机提供的轻量级的同步机制
1,保证可见性
JMM:Java Memory Model,本身是一种抽象的概念并不真实存在,它描述的是一组规范或规则。通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式
JMM:关于同步的规定
①线程解锁前,必须把共享变量的值刷新回主内存
②线程加锁前,必须读取主内存的最新值拷贝到自己的工作内存
③加锁解锁是同一把锁
由于jvm运行程序的实体是线程,而每个线程创建时,jvm都会为其创建一个工作内存,工作内存是每个线程的私有数据区域,Java内存模型中规定所有变量都储存在主内存,主内存是共享内存区域,所有线程都可以访问。但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存中拷贝到自己的工作内存区间,然后对变量进行操作,操作完成后再将变量写回主内存。不能直接操作主内存中的变量,各个线程中的工作内存中储存着主内存中的变量拷贝副本。因此不同的线程间无法放问对方的工作内存,线程间的通信必须通过主内存来完成。
即,当某个线程将变量最新值拷贝回主内存时,所有的线程都第一时间知道并更新该变量值,叫做JMM中的可见性
JMM的三大特性:
①可见性 ②原子性 ③有序性
2,不保证原子性
原子性:不可分割,完整性,中间不可以被分割修改。
volatile不保证原子性,多线程同时操作的时候会引起数据丢失(例如a++,会少加)
如何解决:
①synchronizied 但是太重量级,杀鸡用牛刀
②java.util.conrurrent.atomic类 为什么,用这个就可以解决原子性。主要是CAS(compare and Swap比较并交换),
什么是CAS:
副本中的变量和主内存中原始值比较,如果一致,则Swap
底层原理:
①unsafe类:jdk中lib包中,rt.jar中有这个类。都是native的方法。它是CAS的核心类。由于java无法直接直接访问底层系统,需要通过本地native方法来访问,unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。unsafe内部方法可以像C的指针一样直接操作内存,因为java中的CAS操作的执行依赖于unsafe的方法
②变量valueoffset表示该变量值在内存中的偏移地址,因为unsafe是根据内存偏移地址获取数据的
③变量value用volatile修饰,保证了多线程之间的内存可见性
CAS是一条CPU并发原语,执行必须连续,在执行的过程中不允许被中断,是一条CPU的原子指令,不会造成所谓的数据不一致问题
CAS的缺点:
① 循环时间长,如果CAS失败会一直进行尝试,如果长时间不成功,CPU开销会很大。
②只能保证对一个共享变量进行操作,如果对多个共享变量进行操作时,这时候可以用锁来保证原子性
③引出来ABA问题:
AtomicInteger的ABA问题。狸猫换太子。
CAS算法实现的一个重要的前提是需要取出内存中某时刻的数据并在当下时刻比较并交换,那么在这个时间差内会导致数据的变化。
线程1 操作要10S
线程2 操作要2S,线程2将主内存中的A-》B-》C-》A
线程1的CAS操作还是能成功,但是不代表这个过程是没有问题的。
解决ABA问题:
atomicReference<>原子引用泛型类。你丢一个类进去,这个类就变成了原子类atomicReference 原子用户类。
这样,新增一种机制,修改版本号(类似时间戳)
3,禁止指令重排
即:有序性!
计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排:
源代码-》编译器优化的重排-》指令并行的重拍-》内存系统的重排-》最终执行的命令
单线程环境里面确保程序最终执行结果和代码顺序执行的一致
处理器在进行重排序时必须要考虑指令之间的数据依赖性
多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测