认识java关键字volatile

Java 中 Volatile关键字的作用

     在java多线程中如何保证线程的安全性? 我们可以使用Synchronized同步锁来给需要多个线程访问的代码块加锁以保证线程安全性。使用synchronized虽然可以解决多线程安全问题,但是弊端也明显:加锁后多个线程需要判断锁,
 较为消耗资源, 所以就有个volatile关键字,一种轻量级的解决方案。
 
 想要理解volatile为什么能确保可见性,就要先理解java中的内存模型是什么样的?
 Java内存模型规定了所有的变量都存储在主内存中。 每条线程中还有自己的工作内存, 线程的工作内存中保存了被该线程所使用到的变量(这些变量是从主内存中拷贝而来的)。
 线程对变量的所有操作(读取,赋值)都必须在工作内存中进行。 不同线程之间也无法直接访问对方工作内存中的变量, 线程间变量值的传递均需要通过主内存来完成。
 
 缓存一致性问题?
 如何确保共享变量在多线程访问时能够正确输出结果呢?
 并发编程的三大概念: 原子性,有序性,可见性。
 
原子性: 即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

Java中的原子性
  在java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要不不执行。
  java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。
由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。

三,可见性
可见性: 是指当多个线程访问同一个变量时,一个线程修改了这个变量的值, 其它线程能够立即看得到修改的值。

java中的可见性
  对于可见性, java提供了volatile 关键字来保证可见性。
  当一个共享变量被volatile修饰时, 它会保证修改的值会立即被更新到主存,当有其它线程需要读取时, 它会去内存中读取新值。
而普通的共享变量不能保证可见性, 因为普通共享变量被修改之后, 什么时候被写入主存是不确定的,当其它线程去读取时, 此时内存中
可能还是原来的旧值, 因此无法保证可见性。
另外,通过synchronized和lock也能够保证可见性, synchronized和lock能保证同一时刻只有一个线程获取锁然后执行同步代码,
并且在释放锁之前会将变量的修改刷新到主存当中。 因此可以保证可见性。

四、有序性
 有序性: 即程序执行的顺序按照代码的先后顺序执行。
执行重排序(Instruction Reorder):一般来说, 处理器为了提高程序运行效率,可能会对输入代码进行优化, 它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,
但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

虽然重排序不会影响单个线程内程序执行的结果, 但是多线程的时候会影响。
 要想并发程序正确地执行, 必须要保证原子性,可见性以及有序性。 只要有一个没有被保证,就有可能会导致程序运行不正确。

 java中的有序性
 通过volatile 关键字来保证一定的有序性。
 
 五、深入理解volatile关键字
 1.volatile保证可见性
 一旦①个共享变量(类的成员变量,类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1) 保证了不同线程对这个变量进行操作时的可见性, 即一个线程修改了某个变量的值,这新值对其它线程来说是立即可见的。
2) 禁止进行指令重排序。
voliatle不能确保原子性。
六、volatile的实现原理
1.可见性
    处理器为了提高处理速度,不直接和内存进行通讯,而是将系统内存的数据读到内部缓存后再进行操作,但操作完后不知什么时候会写到内存。
如果对声明了volatile变量进行写操作时,JVM会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写到系统内存,这一步确保了如果有
其它线程对声明了volatile变量进行修改,则立即更新主内存中数据。
但这丝毫其它处理器的缓存还是旧的,所以在多处理器环境下,为了保证各个处理器缓存一致,每个处理会通过嗅探在总线上传播的数据来检查,自己的缓存是否过期,
当处理器要对这个数据进行修改操作时,会强制重新从系统内存把数据读到处理器缓存里,这一步确保了其它线程获得的声明volatile变量都是从主内存中获取最新的。
2.有序性
Lock前缀指令实际上相当于一个内存屏障(内存栅栏),它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到
内存屏障的后面, 即在执行到内存屏障这句指令时,在它前面的操作已经全部完成。
七、volatile的应用场景
synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些情况下性能优于synchronized,
但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。
通常来说,使用volatile必须具备以下2个条件:
1) 对变量的写操作不依赖于当前值。
2)该变量没有包含在具有其它变量的不变式中。
下面列举几个java中使用volatile的几个场景。
1. 状态标记量
2.单例模式中的doublecheck

你可能感兴趣的:(JAVA)