美文网首页
JAVA内存模型笔记(JMM)

JAVA内存模型笔记(JMM)

作者: Duskry | 来源:发表于2020-04-18 11:47 被阅读0次

    JAVA内存模型

    这里做的笔记是结合JVM中的java内存模型

    和java并发编程艺术中讲的java内存模型

    再结合一些面试题

    JVM内存区域和JAVA内存模型有明显的区别

    要分清他们之间的关系

    JMM是一种规则,主要是研究并发多线程内存的可见性

    是一种高速缓存进行读写访问的过程抽象

    目的就是保证程序运行时内存应该是==内存一致==的

    在这里插入图片描述

    主内存和工作内存

    Java内存模型为每个线程都创建了一个工作内存

    但是所有的变量都应该存储在主内存中,这里的主内存不是硬件上的内存。

    而是JVM中的一部分


    在这里插入图片描述

    Volatile 的内存语义

    语义1 对Volatile变量修改对其他线程是立即可见的

    但使用Volatile变量的计算==并不是==线程安全的

    例如如果多线程对volatile变量自增而不加锁,就会同步出错

    因为

    num++其实是由4个字节码操作组成的。位于栈顶的num很可能被其他线程加大。

    问题解决:

    1.可以使用AtomicInteger 自增操作则是 incrementAndGet()方法

    2.方法synchronized

    使用场景:

    1.运算结果不依赖当前的值,或者单线程改变 比如set get 方法 不依赖原值

    2.变量不需要其他线程参与变量的约束

    语义2 Volatile 禁止指令重排序优化

    volatitle boolean  config = false ;
    
    finishconfig(config);
       
    config = true;
    
    while(!config){
        sleep();
    }
    
    dosomethings();
    

    如果 config 不是Volatile变量

    config可能比 fhinishconfig还快

    这就可能导致还没配置就执行其他的了。

    总结一下volatile 变量就三个特性

    本身的读和写是原子的,其他的不是比如自增==原子性==

    对Volatile变量操作是所有线程可见的,强制刷新缓存 ==可见性==

    Volatile 变量不可以被重排序添加内存屏障 ==有序性==

    锁的内存语义

    锁的释放和获取本质上就是消息通知

    一个线程释放锁,是告诉下一个获取线程的锁消息。

    一个线程获取锁,其实是接受了某个线程发出的锁消息。

    sychronized 本质上就是加锁 和 lock 方法类似

    sychronized 里的操作是原子的,因为只有一个线程执行==原子性==

    sychronized 操作完是对所有线程可见的 对一个变量unlock会同步变量到主内存==可见性==

    sychronized 里的代码不能被重排序因为只有一个线程执行 ==有序性==

    一些锁的实现本质上就是 修改Volatile变量 进行CAS操作来达到内存一致性的

    final域的内存语义

    final不同于锁和Volatile

    final域访问类似普通变量

    但 final 域必须遵守两个重排序规则

    1. 构造函数里对final域的写入,与随后这个对象的引用赋值给一个引用变量,这个操作不能重排序
    2. 第一次读一个final 域 的引用,和随后读这个final域,这个操作不能重排序
    // 一个对象
    static Ex object;
    // 构造函数
    public Ex(){
        // i 是 final域
        i=0;
        // j 是普通域
        j=2;
    }
    // A线程写
    public void write(){
        object = new Ex();
    }
    // B 线程读
    public void read(){
        Ex obj  = object ;
        int a = obj.i;
        int b = obj.j;
    }
    

    看上面这个例子

    B 线程对 obj.i 即final 域 一定读出来是 构造函数初始化过后的值。

    而对 obj.j 即普通域不一定读出来初始化的值,而可能是默认初值。

    ==final 域保证对象的值或者引用一定是正确初始化过后的值==

    ==final域保证读对象的final域之前一定先读对象的引用==

    happens-before

    先行规则是判断数据是否存在竞争,线程是否安全的重要原则。

    1. 程序顺序 在单线程内按顺序流执行
    2. 管程锁定(监视器锁) 对一个锁的解锁在这个锁加锁之前
    3. volatile原则 对Volatile变量的写在这个变量的读之前
    4. 线程开始原则 线程的启动方法start()先于所有线程内的操作之前执行
    5. 线程终止原则 线程所有的操作比 线程终止先执行 如Thread.join结束方法
    6. 线程中断原则 对线程interrupted()操作先于 中断线程中的检测中断事件
    7. 对象终结原则 一个对象的构造方法先于他的finalize方法
    8. 传递性 , A 比 B 先,B比C 先 ,那么A比C 先

    双重检查锁定 DCL

    double check lock简称 DCL是非常常见的延迟加载技术

    即 对于某些开销大的对象,在使用的时候才加载。按需加载。也叫懒加载

    下面是线程不安全的初始化对象

    public class LazyInital {
        class Instatnce{
            
        }
        private static Instatnce instatnce;
        
        public Instatnce getInstatnce(){
            if(instatnce==null)      // Thread A
                instatnce = new Instatnce();  //  Thread B
            return instatnce;
        }
    }
    

    简单的改就在getInstance 方法上加sychronized标识即可。

    但这样性能不好

    下面是DCL的错误版本

    public Instatnce getInstatnce(){
        if(instatnce==null){
            synchronized (LazyInital.class){
                if(instatnce==null)
                    instatnce = new Instatnce();
            }
        }
        return instatnce;
    }
    

    这样就叫双重检查锁定 看上去很舒服但是有大问题

    java对象的初始化 分为3个部分

    1. memory = allocate() // 分配内存
    2. initclass(memory) // 初始化对象
    3. instance = memory // 指向对象

    2 和3 是可能被重排序的。

    最后的一种情况可能是

    线程A 还没有初始化instance 线程B就已经拿到对象的引用了,此时返回的是一个没有初始化的对象

    解决方法

    1. 禁止 初始化对象 和指向对象 重排序

    使用 Volatile的语义即可
    加上Volatile 描述

    1. A线程对象的初始化可以重排序,但对线程B应该是不可见的。

    使用类初始化的解决方法,依靠JVM初始化类的对象加锁的特性

    public class InstanceFactory {
        static class Instance{
    
        }
        // instanceHolder类的初始化加锁
        private static class InstanceHolder{
            private static Instance instance = new Instance();
        }
        public static Instance getInstance(){
            return InstanceHolder.instance;
        }
    }
    

    volatile和synchronized的区别

    1. volatile的本质是告诉jvm当前变量在工作内存中的值不确定,要从主内存中读取

      而synchronized是锁定当前变量,只能由一个线程读取,其他线程阻塞,直到锁释放。

    2. Volatile 变量级别,synchronized 方法变量

    3. Volatile只能实现变量修改的可见性,不能保证操作的原子性。比如Volatile的读和写是原子的,但Volatile操作不一定是原子的,自增。

    4. Volatile不会阻塞,synchronized会阻塞

    5. volatitle变量不会优化,synchronized会优化

    相关文章

      网友评论

          本文标题:JAVA内存模型笔记(JMM)

          本文链接:https://www.haomeiwen.com/subject/jflvvhtx.html