Java内存模型

📝 个人主页:程序员阿红🔥

🎉 支持我:点赞👍收藏⭐️留言📝

1. Java内存模型

Java 内存模型中规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。

这里的工作内存是 JMM 的一个抽象概念,也叫本地内存,其存储了该线程以读/写共享变量的副本。就像每个处理器内核拥有私有的高速缓存,JMM 中每个线程拥有私有的本地内存。

不同线程之间无法直接访问对方工作内存中的变量,线程间的通信一般有两种方式进行,一是通过消息传递,二是共享内存。Java 线程间的通信采用的是共享内存方式,线程、主内存和工作内存的交互关系如图:

在这里插入图片描述

JMM 体现在以下几个方面

  • 原子性 - 保证指令不会受到线程上下文切换的影响
  • 可见性 - 保证指令不会受 cpu 缓存的影响
  • 有序性 - 保证指令不会受 cpu 指令并行优化的影响

1.1原子性

定义: 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

原子性是拒绝多线程操作的,不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作。简而言之,在整个操作过程中不会被线程调度器中断的操作,都可认为是原子性。例如 a=1是原子性操作,但是a++和a +=1就不是原子性操作。

1.2可见性

1.2.1退不出的循环

package com.example.JMM;

/**
 * 退不出的循环
 */
public class Code_01_Test {
    // 易变
    static  boolean run = true;

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while(true){
                if(!run) {
                    break;
                }
            }
        });
        t.start();

        Thread.sleep(1000);
        run = false; // 线程t不会如预想的停下来
    }
}

  • 程序在执行中并不会退出while循环,这是为什么呢?分析如下

在这里插入图片描述

t线程执行while循环,1秒过后,主线程run的值修改为false,想让t线程停下来,但是程序并没有停下来。

共享变量run存储在主存中,主线程和t线程共享该变量,分别缓存在自己的工作内存中。主线程修改的run值只会保存在自己的工作内存中,并不会同步到主存中,其次t线程也只会读取自己本地内存的run值,所以导致该while循环无法退出。

  • 解决方法:
    • 使用 volatile (易变关键字)

volatile主要作用:解决可见性问题

  • 那什么又是可见性呢?

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

  • volatile它可以用来修饰成员变量和静态成员变量(放在主存中的变量),

  • 他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,

  • 线程操作 volatile 变量都是直接操作主存

package com.example.JMM;

/**
 * 退不出的循环:
 */
public class Code_02_Test {
    // 易变
    static volatile boolean run = true;
//    注意 synchronized 语句块既可以保证代码块的原子性,
//    也同时保证代码块内变量的可见性。但缺点是 synchronized
//    是属于重量级操作,性能相对更低。
//    synchronozied(JmmTest2.class){
//         if(!run) {
//                break;
//            }
//  }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while(true){
                if(!run) {
                    break;
                }
            }
        });
        t.start();

        Thread.sleep(1000);
        run = false; 
    }
}

1.2.2可见性原理

  • volatile 的底层实现原理是内存屏障,Memory Barrier(Memory Fence)
  • 对 volatile 变量的写指令后会加入写屏障
  • 对 volatile 变量的读指令前会加入读屏障

1)写屏障(sfence)保证在该屏障之前的写操作都会同步到主存中。

public void actor2(I_Result r) {
     num = 2;
     ready = true; // ready 是被 volatile 修饰的,赋值带写屏障
     // 写屏障
}
  1. 而读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中的最新数据。
public void actor1(I_Result r) {
 // 读屏障
 // ready是被 volatile 修饰的,读取值带读屏障
 if(ready) {
 	r.r1 = num + num;
 } else {
 	r.r1 = 1;
 }
}

1.3 有序性

  • 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后

  • 读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前

public void actor1(I_Result r) {
 // 读屏障
 // ready 是被 volatile 修饰的,读取值带读屏障
 if(ready) {
 	r.r1 = num + num;
 } else {
 	r.r1 = 1;
 }
}

注意:volatile 不能解决指令交错

写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证其它线程的读跑到它前面去。

而有序性的保证也只是保证了本线程内相关代码不被重排序

2.double-checked-locking问题

public class Double_Check_Lock {
    public static void main(String[] args) {
        Singleton singleton1 = new Singleton();
        Singleton singleton2 = new Singleton();
        System.out.println(singleton1.getSingleton() == singleton2.getSingleton());
    }
}
class Singleton{
    public static Singleton singleton = null;
    public Singleton(){}
    public static Singleton getSingleton(){
        // 使用synchronized来保证线程安全问题
        synchronized (Singleton.class){
            if (singleton == null){
                singleton = new Singleton();
            }
        }
        return singleton;
    }

}

此方法还有些许不足,

  • singleton是共享变量,使用了synchronized来进行保障线程安全问题,但是只要访问getSingleton就会对Singleton类对象加锁。
  • synchronized是重量级锁,所以其性能会有所降低。
  • 注意在 JDK 5 以上的版本的 volatile 才会真正有效。

3.double-checked locking 解决

  • 加volatile就行了。
public class Double_Check_Lock_plus {
    public static void main(String[] args) {
        Singleton singleton1 = new Singleton();
        Singleton singleton2 = new Singleton();
        System.out.println(singleton1.getSingleton() == singleton2.getSingleton());
    }
}
class SingletonPlus{
    public static volatile  SingletonPlus singleton = null;
    public SingletonPlus(){}
    public static SingletonPlus getSingleton() {
        if (singleton == null) {  //t2
            // 使用synchronized来保证线程安全问题
            synchronized (SingletonPlus.class) {
                if (singleton == null) { //t1
                    singleton = new SingletonPlus();
                }
            }
        }
        return singleton;
    }
}

1.为什么还要在synchronized外再加一个if (singleton == null)条件?

答:防止在此调用getSingleton方法还是会对其上锁,会严重影响其性能

2.但是if(INSTANCE == null)判断代码没有在同步代码块synchronized中

不能享有synchronized保证的原子性,可见性。(synchronized可以保证

其重排序,但是所有访问的共享资源必须都在synchronized内)

好比下图 t1线程处运行至此,t2线程运行到该位置。t1实例化该对象后,t2

线程并不能立即识别到singleton已经被实例化。

解决办法:增加volatile对其singleton对象来保证可见性。

💖💖💖 完结撒花

💖💖💖 路漫漫其修远兮,吾将上下而求索

✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

最后,还不动手进你的收藏夹吃灰😎😎😎

🎉 支持我:点赞👍收藏⭐️留言📝

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员阿红

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值