java内存模型

并发编程模型的分类

  • 并发编程中遇到的两个问题: 线程之间如何通信, 线程之间如何同步
  • 以线程之间的通信模式来看, 并发模型可以分为两种
    ** 共享内存的并发模型
    ** 消息传递的并发模型
  • java并发采用的是共享内存模型

JMM抽象模型

  • java中, 堆内存是线程之间共享的, 其中包括了实例域、静态域、数组元素, 而局部变量、
    方法参数、异常处理参数是线程私有的, 不会有内存可见性问题, 不受内存模型影响
  • JMM决定了一个线程对共享变量的写入何时对另一个线程可见
  • 从抽象角度来看, 共享变量存储在主内存中, 每个线程有一个副本, 读写在这个副本上, 最终由JMM决定与主内存的同步
    ** 线程A向主内存刷新变更, 线程B向主内存读取更新, 实现线程通信
  • java内存模型

重排序

  • 编译器优化重排序
  • 指令级并行的重排序
  • 内存系统的重排序
  • JMM属于语言级的内存模型, 它确保了在不同编译器与不同处理器平台上, 通过禁止特定的重排序, 为程序员提供一致的内存可见性

内存屏障

实现cpu数据可见性, 禁止指令重排序

java内存模型如何定义并发关键字的行为

  • synchronized
    ** 线程释放monitor, 把cpu缓存(线程本地缓存)刷新到主内存
    ** 线程获得monitor, 把cpu缓存失效, 强制从主内存获取
    ** 禁止特定的指令重排, synchronized关键字包裹的代码不可以重排序到外面
  • volatile
    ** volatile修饰的变量, 每次的变更都会刷新到主内存, 每次读取这种变量前, 也必须保证缓存无效
    ** 禁止特定的指令重排
  • final
    ** final修饰的变量, 在正确的构造对象后, 对其它线程是可见的

happens-before

双重检查锁定(double-check-locking)

当我们写出一个标准的双重检查锁定单例模式时(如下), 代码仍是有问题的, 这是因为重排序的缘故,
线程有可能拿到尚未实例化的对象的引用.
使用volatile修饰类变量, 禁止helloSingleton = new HelloSingleton()这步的指令重排.

    private static HelloSingleton helloSingleton;
    public static HelloSingleton instance() {
        if (helloSingleton == null) {
            synchronized(SingletonFactory.class) {
                if (helloSingleton == null) {
                    return helloSingleton = new HelloSingleton();
                }
            }
        }
        return helloSingleton;
    }

还有一种更加优雅的方式, 使用内部类实现, 内部类会在被引用时才加载, 实现了懒加载

单例模式实现方式有好多种,但大部分都会有多线程环境下的问题;使用内部类可以避免这个问题,因为在多线程环境下,jvm对一个类的初始化会做限制,同一时间只会允许一个线程去初始化一个类,这样就从虚拟机层面避免了大部分单例实现的问题

    public static class innerHolder {
        public static HelloSingleton helloSingleton = new HelloSingleton();
    }
    public static HelloSingleton instanceV5() {
        return innerHolder.helloSingleton;
    }

参考资料

深入理解Java内存模型(一)——基础

什么是Java内存模型

双重检查锁定

posted on 2018-03-22 00:07  simple_huang  阅读(212)  评论(0编辑  收藏  举报