高并发的可见性搞不明白,就不用再研发了( 二 )


单线程环境里面每次执行代码只有一个线程 , 也就确保了程序最终执行结果和代码顺序执行的结果一致 。 (某些存在的竞态环境也会触发) 在多线程环境中由于线程交替执行 , 编译器优化重排的存在 , 两个线程中使用的变量能否保证一致性时无法确定的 , 导致了结果无法预测 。
高并发的可见性搞不明白,就不用再研发了文章插图
代码实例:
描述:在公共类中声明成员变量:int a,b,x,y=0
高并发的可见性搞不明白,就不用再研发了文章插图
说明:常规情况下 , 线程按照正常的逻辑先后执行 。 在高并发下 , 如果编译器对这段程序代码执行重排优化后 , 可能出现如下情况:
高并发的可见性搞不明白,就不用再研发了文章插图
说明:在多线程环境下 , 由于编译器优化重排的存在 , 两个线程中使用的变量能否保证一致性是无法确定的 。
volatile可以实现禁止指令重排 , 从而避免了多线程环境下程序出现乱序执行的现象
内存屏障(Memory Barrier)又称内存栅栏 , 是一个CPU指令 , 他的作用有两个:

  • 保证特定操作的执行顺序
  • 保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)
由于编译器和处理器都能执行指令重排优化 。 如果在执行前Memory Barrier则会告诉编译器和CPU , 不管什么指令都不能和这条Memory Barrier指令重排顺序 , 也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化 。 内存屏障另外一个作用是强制刷出各种CPU的缓存数据 , 因此任何CPU上的线程都能读取到这些数据的最新版本 。 (本质上由于高速缓存禁止了)
高并发的可见性搞不明白,就不用再研发了文章插图
JMM(java内存模型)
JMM(Java Memory Model)本身是一种抽象的概念 , 并不真实存在 , 他描述的时一组规则或规范 , 通过这组规范定义了程序中各个变量(包括实例字段 , 静态字段和构成数组对象的元素)的访问方式 。
JMM关于同步的规定
① 线程解锁前 , 必须把共享变量的值刷新回主内存
② 线程加锁前 , 必须读取主内存的最新值到自己的工作内存
③ 加锁解锁时同一把锁
解析:由于JVM运行程序的实体是线程 , 而每个线程创建时JVM都会为其创建一个工作内存(有的成为栈空间) , 工作内存是每个线程的私有数据区域 , 而java内存模型中规定所有变量都存储在JVM主内存 , 主内存是共享内存区域 , 所有线程都可以访问 , 但线程对变量的操作(读取赋值等)必须在私有内存区域中进行 。 每个线程在这个过程中都要将变量从主内存拷贝到自己的独占内存空间 , 在私内存中对变量副本进行操作 , 操作完成后再将变量副本写回主内存 。 由于不能直接操作主内存中的变量 , 各个线程中的工作内存中存储着主内存的变量副本 , 因此不同的线程件无法访问对方的工作内存 , 线程间的通信(传值)也必须通过主内存来完成 。 Voliate可以很好地保证共享变量在线程之间的可见性 。
volatile的使用
当普通单例模式在多线程情况下:
public class SingletonDemo {private static SingletonDemo instance = null;private SingletonDemo() {System.out.println(Thread.currentThread().getName() + "\t 构造方法SingletonDemo()");}public static SingletonDemo getInstance() {if (instance == null) {instance = new SingletonDemo();}return instance;}public static void main(String[] args) {//构造方法只会被执行一次//System.out.println(getInstance() == getInstance());//System.out.println(getInstance() == getInstance());//System.out.println(getInstance() == getInstance());//并发多线程后 , 构造方法会在一些情况下执行多次for (int i = 0; i < 10; i++) {new Thread(() -> {SingletonDemo.getInstance();}, "Thread " + i).start();}}}说明:其构造方法在多线程情况下可能会被执行多次
利用【volatile】的解决方式:
单例模式DCL代码
DCL (Double Check Lock双端检锁机制)在加锁前和加锁后都进行一次判断
public static SingletonDemo getInstance() {if (instance == null) {synchronized (SingletonDemo.class) {if (instance == null) {instance = new SingletonDemo();}}}return instance;}大部分运行结果构造方法只会被执行一次 , 但指令重排机制会让程序很小的几率出现构造方法被执行多次 。 DCL(双端检锁)机制不一定能线程安全 , 由于线程执行速度快慢有别的原因或指令重排序 , 可能导致重入的风险 。