Java内存模型 vs  JVM运行时数据区

为什么java 能在不同的平台上跑?因为字节码是在虚拟机上跑,所以平台无关。遵循《Java虚拟机规范》 

初看Java内存模型

来看看大家都是怎么定义的 :

Java虚拟机可以同时支持多个执行线程,若未正确同步,线程的行为可能会出现混淆和违反直觉。

多线程程序的语义,它包含了,当多个线程修改了共享内存中的值时,应该读取到哪个值的规则。由于这部分规范类似于不同硬件体系结构的内存模型,因此这些语义称为Java编程语言内存模型。

这些语义没有规定如何执行多线程程序。相反,它们描述了允许多线程程序的合法行为。

多线程中的问题

  1. 所见非所得
  2. 无法肉眼去检测程序的准确性
  3. 不同的运行平台有不同的表现
  4. 错误很难重现

有两个线程t1 和t2 ,一个标记, 当t1更改标记后,t2 没有读到更改后的标记。加上volatile关键字就收到了,示例代码如下。

public class Test1 {
    private volatile boolean  flag = true ;
    public static void main(String[] args) throws InterruptedException {
        Test1 demo1 = new Test1();
        System.out.println("代码开始了");
        new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 0 ;
                while (demo1.flag){
                    i ++;
                }
                System.out.println("run i = "+ i);
            }
        }).start();
        TimeUnit.SECONDS.sleep(2);
        demo1.flag = false ;
        System.out.println("被设置为false了");

    }
}

//客户端输出
代码开始了
被设置为false了
run i = 201004010
复制代码

flag 的值 ,没有读到 ,可能原因是主线程没写入,或者副线程没读到。 flag 放在CPU的高速缓存中,时间片上有一瞬间值不一致,很快,所以不是高速缓存的锅  ,那是谁的呢,CPU指令重排。

CPU指令重排序

Java编程语言的语义允许Java编译器微处理器进行执行优化,这些优化导致了与其交互的代码不再同步,从而导致看似矛盾的行为。

单线程的指令重排-没有问题

循环的效率大于缓存,cpu想出来用赋值进行指令重排,A=0, B=0 。

多线程的指令重排-有问题

0012 变成了 1122 。

JIT编译器(Just In Time Compiler)

脚本语言与编译语言的区别 ?

解释执行:即咱们说的脚本,在执行时,由语言的解释器将其一条条翻译成机器可识别的指令 。翻译的价格有200块,500块不等。

编译执行:将我们编写的程序,直接编译成机器可以识别的指令码。

_Java是脚本语言还是编译语言?Java_介于脚本语言编译语言之间 

volatile 关键字

可见性问题:让一个线程对共享变量的修改,能够及时的被其他线程看到。

Java内存模型规定

对volatile变量v的写入,与所有其他线程后续对v的读同步

要满足这些条件,所以volatile关键字就有这些功能:

禁止缓存:

  1. volatile变量的访问控制符会加个ACC_VOLATILE 从链接得知 ACC_VOLATILE 0x0040 Declared volatile; cannot be cached.

       docs.oracle.com/javase/spec…

  1. 对volatile变量相关的指令不做重排序

Shared Variables 定义

可以在线程之间共享的内存成为共享内存或堆内存

所有实例字段静态字段数组元素都存储在堆内存中,这些字段和数组都是标题中提到的共享变量

冲突:如果至少有一个访问是写操作,那么对同一个变量的两次访问是冲突的

这些能被多个线程访问的共享变量是内存模型规范的对象。

线程间操作的定义

  1. 线程间操作指:一个程序执行的操作可被其他线程感知或被其他线程直接影响
  2. Java内存模型只描述线程间操作,不描述线程内操作,线程内操作按照线程内语义执行。

所有线程间操作,都存在可见性问题,JMM需要对其进行规范

对于同步的规则定义

  • volatile变量v的写入,与所有其他线程后续对v的读同步
  • 对于监视器m的解锁与所有后续操作对于m的加锁同步
  • 对于每个属性写入默认值(0,false,null)与每个线程对其进行的操作同步
  • 启动线程的操作与线程中的第一个操作同步
  • 线程T2的最后操作与线程T1发现线程T2已经结束同步。(isAlive,join可以判断线程是否终结)

Happens-before先行发生原则

final 在JMM 中的处理

Word Tearing 字节处理

double和long的特殊处理

再看Java内存模型


原文链接:https://juejin.cn/post/7054944423875969060