Volatile是Java虚拟机提供轻量级的同步机制
- 保证可见性
- 不保证原子性
- 禁止指令重排
可见性
可见性与Java的内存模型(JMM)有关,模型采用缓存与主存的方式对变量进行 *** 作,也就是说,每个线程都有自己的缓存空间,对变量的 *** 作都是在缓存中进行的,之后再将修改后的值返回到主存中,这就带来了问题,有可能一个线程在将共享变量修改后,还没有来的及将缓存中的变量返回给主存中,另外一个线程就对共享变量进行修改,那么这个线程拿到的值是主存中未被修改的值,这就是可见性的问题。
volatile很好的保证了变量的可见性,变量经过volatile修饰后,对此变量进行写 *** 作时,汇编指令中会有一个LOCK前缀指令,这个不需要过多了解,但是加了这个指令后,会引发两件事情:
- 将当前处理器缓存行的数据写回到系统内存
- 这个写回内存的 *** 作会使得在其他处理器缓存了该内存地址无效
意思就是说当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值,这就保证了可见性。
eg://没有使用volatile,Thread1线程对主内存中的变价不知道,加了volatile可以保证可见性
package lin.jmm; import java.util.concurrent.TimeUnit; public class Test01 { private static int num=0; public static void main(String[] args) { new Thread(()->{ while (num==0){ } },"Thread1").start(); //停一秒,使Thread1线程启动 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } num=1; System.out.println(num); } }
eg://使用volatile
package lin.jmm; import java.util.concurrent.TimeUnit; public class Test01 { private volatile static int num=0; public static void main(String[] args) { new Thread(()->{ while (num==0){ } },"Thread1").start(); //停一秒,使Thread1线程启动 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } num=1; System.out.println(num); } }
原子性(不可分割)
首先需要了解的是,Java中只有对基本类型变量的赋值和读取是原子 *** 作,如i = 1的赋值 *** 作,但是像j = i或者i++这样的 *** 作都不是原子 *** 作,因为他们都进行了多次原子 *** 作,比如先读取i的值,再将i的值赋值给j,两个原子 *** 作加起来就不是原子 *** 作了。
线程A在执行任务的时候,不能被打扰,不能被分割,要么同时成功,要么同时失败。
所以,如果一个变量被volatile修饰了,那么肯定可以保证每次读取这个变量值的时候得到的值是最新的,但是一旦需要对变量进行自增这样的非原子 *** 作,就不会保证这个变量的原子性了。
package lin.volataile_Test; //不保证原子性 public class Test02 { private volatile static int sum=0; public static void add(){ sum++; } public static void main(String[] args) { for (int i = 1; i <= 20; i++) { new Thread(()->{ for (int j = 0; j < 1000; j++) { //正常sum为2万 add(); } }).start(); } while (Thread.activeCount()>2){ Thread.yield(); } System.out.println(Thread.currentThread().getName()+" "+sum); } }
加了synchronized 或 lock锁,就可以保证原子性
如果不使用 lock 和 synchronized,怎么保证原子性?
使用原子类,解决原子性问题
package lin.volataile_Test; import java.util.concurrent.atomic.AtomicInteger; //不保证原子性 public class Test02_AtomicInteger { private volatile static AtomicInteger sum = new AtomicInteger(); public static void add(){ //sum++; 因为是一个类,所以不能直接++ *** 作 sum.getAndIncrement(); //AtomicInteger 的+1方法: CAS } public static void main(String[] args) { for (int i = 1; i <= 20; i++) { new Thread(()->{ for (int j = 0; j < 1000; j++) { //正常sum为2万 add(); } }).start(); } while (Thread.activeCount()>2){ Thread.yield(); } System.out.println(Thread.currentThread().getName()+" "+sum); } }
这些类的底层都是直接和 *** 作系统挂钩!在内存中修改值!Unsafe类是一个很特殊的存在!
指令重排
什么是指令重排?
源代码-->编译器优化的重排-->指令并行也可能会重排-->内存系统也会重排-->执行
处理器在进行指令重排的时候,会考虑数据之间的依赖性!
volatile可以避免指令重排问题:
内存屏障。CPU指令。作用:
1、保持特定的 *** 作的执行顺序!
2、可以保证某些变量的内存可见性(利用这些特性volatile实现了可见性)
只要加了volatile就会在上面跟下面加一个内存屏障
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)