java内存模型

java内存模型(Java Memory Model,简称JMM)隶属于JVM(Java虚拟机),它定义了Java虚拟机在计算机内存(RAM)中的工作方式,也就是说它规范了Java虚拟机与计算机内存之间是如何协同工作的。JMM规定了如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步地访问共享变量 。

JMM把JVM内部划分为线程栈和堆,如下图所示:

这里写图片描述

每个运行在JVM中的线程都拥有自己的线程栈(Thread Stack),线程仅能访问自己的线程栈,且创建的本地变量仅对自己可见。

所有原始类型(boolean,byte,short,char,int,long,float,double)的本地变量都直接保存在线程栈当中,它们的值在各个线程之间独立。也就是说,一个线程可以传递一个原始类型的本地变量的副本给另一个线程,但它们之间是无法共享这个本地变量本身的。

堆区包含了Java应用创建的所有对象的信息,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。不管对象是哪个线程创建的,属于一个成员变量还是方法中的本地变量,都会被存储在堆区。

一个本地变量也有可能是一个对象的引用,这种情况下,这个本地引用会被存储到栈中,但是对象本身仍然存储在堆区(比如下图中的本地变量localVariable2和它所指向的对象object3)。

一个对象的成员方法中的本地变量存储在栈区;一个对象的成员变量不管是原始类型还是包装类型,都会被存储到堆区。

堆中的对象可以被多个线程共享,如果一个线程获得一个对象的引用,它便可访问这个对象的成员变量(比如本地变量localVariable2存储了Object3对象的引用,所以它可以访问Object3对象的成员变量object2和object4)。如果两个线程同时调用了同一个对象的同一个方法,那么这两个线程便可同时访问这个对象的成员变量,但是对于本地变量,每个线程都会拷贝一份到自己的线程栈中。

以上描述总结如下图:

这里写图片描述


public class MyRunnable implements Runnable() {

    public void run() {
        methodOne();
    }

    public void methodOne() {
        //localVariable1是原始类型的本地变量,保存在线程栈中
        int localVariable1 = 45;
        //localVariable2是对象的引用,指向堆上的对象Object3
        Object3 localVariable2 = Object3.object3;
        methodTwo();
    }

    public void methodTwo() {
        //localVariable3是对象的引用
        Integer localVariable3 = new Integer(99);
    }
}

public class Object3 {
    //通过静态变量设置localVariable2指向一个对象引用,仅存在一个静态变量的一份拷贝,localVariable2的两份拷贝都指向同一个实例
    public static final Object3 object3 =
        new Object3();

    //成员变量不管是原始类型还是包装类型,都随对象存放在堆上
    public Integer object2 = new Integer(22);
    public Integer object4 = new Integer(44);
    public long member1 = 12345;
    public long member2 = 67890;
}

Java内存模型运行在计算机硬件上,计算机硬件内存架构如下图所示:

这里写图片描述

现代计算机一般都有多个CPU,而且每个CPU还有可能包含多个核心。因此,如果我们的应用是多线程的话,这些线程可能会在各个CPU核心中并行运行。

CPU Registers : CPU内部的一组CPU寄存器,也就是CPU的储存器。

CPU Cache Memory : CPU缓存,存在于主存和CPU寄存器之间,某些CPU可能有多个缓存层(一级缓存和二级缓存)。

RAM - Main Memory : 计算机的主存也称作RAM,所有的CPU都能够访问主存,而且主存比上面提到的缓存和寄存器大很多。

CPU操作寄存器速度 > 操作CPU缓存的速度 > 操作计算机主存速度。

当一个CPU需要访问主存时,会先读取一部分主存数据到CPU缓存,进而在读取CPU缓存到寄存器。当CPU需要写数据到主存时,同样会先flush寄存器到CPU缓存,然后再在某个时间点把缓存数据flush到主存。

Java内存模型与计算机硬件内存架构关系如下图所示:

这里写图片描述

对于硬件内存架构,所有的线程栈和堆都分布在主存中,部分线程栈和堆可能有时候会出现在CPU缓存中和CPU内部的寄存器中。

当对象和变量存储到计算机的各个内存区域时,主要的两个问题是:

1.线程对共享对象修改的可见性。
2.race condition:两个或多个进程读写某些共享数据,而最后的结果取决于进程运行的精确时序,称为竞争条件。

线程对共享对象修改的可见性

当多个线程同时操作同一个共享对象时,如果没有合理的使用volatile和synchronized关键字,一个线程对共享对象的更新有可能导致其它线程不可见。

比如线程1先从主存中读取共享对象obj到缓存,之后对该共享对象的count属性做修改操作,此时共享变量的新值仍未flush到主存,若有其他线程从主存中访问该共享对象,则无法看到最新值,也就是说线程1对共享对象的修改对于其他线程是不可见的。

这个问题可以考虑使用Java中的volatile关键字,它可以保证直接从主存中读取一个变量,如果这个变量被修改后,总是会被写回到主存中去。

race condition

比如现在有线程1和线程2两个线程,它们都读取主存中的同一共享对象到缓存中,并对该共享对象的count属性执行+1操作。

如果这两个线程是串行执行的话,count的值最终会+2;
如果它们是并行执行的话,count的值最终只会+1。

可以考虑使用java中的synchronized代码块来解决这个问题,它可以保证同一个时刻只能有一个线程进入代码竞争区,也能保证代码块中所有变量都将会从主存中读,当线程退出代码块时,对所有变量的更新将会flush到主存,不管这些变量是不是volatile类型的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

盛夏温暖流年

可以赏个鸡腿吃嘛~

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

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

打赏作者

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

抵扣说明:

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

余额充值