JVM还没学明白,又来了JMM!

简介: JVM还没学明白,又来了JMM!

JMM(Java Memory Model,Java内存模型),是一种抽象的概念 并不真实存在,它描述的是一组规则或规范通过规范定制了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式.

JMM关于同步规定:

  • 线程解锁前,必须把共享变量的值刷新回主内存
  • 线程加锁前,必须读取主内存的最新值到自己的工作内存
  • 加锁解锁是同一把锁

由于JVM运行程序的实体是线程,每个线程创建时JVM都会为其创建一个工作内存(有些地方成为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域所有线程都可访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作空间,然后对变量进行操作,操作完成再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存储存着主内存中的变量副本拷贝,因此不同的线程无法访问对方的工作内存,此案成间的通讯(传值) 必须通过主内存来完成,其简要访问过程如下图:

综上,JMM内部就要保持程序的可见性、原子性和有序性

1 可见性(Visibility)

1.1 描述

通过前面对JMM的介绍,我们知道各个线程对主内存中共享变量的操作都是各个线程各自拷贝到自己的工作内存操作后再写回主内存中的.这就可能存在一个线程AAA修改了共享变量X的值还未写回主内存中时 ,另外一个线程BBB又对内存中的一个共享变量X进行操作,但此时A线程工作内存中的共享比那里X对线程B来说并不不可见.这种工作内存与主内存同步延迟现象就造成了可见性问题.

1.2 实现可见性代码

不加volatile情况下

public class Test01 {
    int i = 100;
    void methodA() {
        this.i = 0;
    }
    public static void main(String[] args) {
        Test01 data = new Test01();
        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            data.methodA();
        }).start();
        while (data.i == 100) {
            //while里不能写代码,因为可能会造成线程调度,main线程由等待变为执行时就会主动获取修改后的值
        }
        System.out.println("0");
    }
}

运行结果:

加上volatile情况下:

public class Test01 {
    volatile int i = 100;
    void methodA() {
        this.i = 0;
    }
    public static void main(String[] args) {
        Test01 data = new Test01();
        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            data.methodA();
        }).start();
        while (data.i == 100) {
            //while里不能写代码,因为可能会造成线程调度,main线程由等待变为执行时就会主动获取修改后的值
        }
        System.out.println("0");
    }
}

运行结果:

2 原子性(Atomicity)

volatile不保证原子性,synchronized既保证可见性又保证原子性

2.1 描述

原子性是指不可分割,完整性,也即某个线程正在做某个具体业务时,中问不可以被加塞或者被分割。需要整体完整要么同时成功,要么同时失败。

2.2 实现原子性代码

验证volatile不保证原子性

public class Test01 {
    volatile int i = 0;
    void add() {
        this.i++;
    }
    public static void main(String[] args) {
        Test01 data = new Test01();
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    data.add();
                }
            }).start();
            new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    data.add();
                }
            }).start();
        }
        while (Thread.activeCount() > 2) {
            Thread.yield();//线程等待
        }
        System.out.println(data.i);
    }
}

结果:

使用synchronized保证原子性

public class Test01 {
    volatile int i = 0;
    synchronized void add() {
        this.i++;
    }
    public static void main(String[] args) {
        Test01 data = new Test01();
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    data.add();
                }
            }).start();
            new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    data.add();
                }
            }).start();
        }
        while (Thread.activeCount() > 2) {
            Thread.yield();//线程等待
        }
        System.out.println(data.i);
    }
}

结果:

使用AtomicInteger保证原子性

public class Test01 {
    AtomicInteger atomicInteger=new AtomicInteger();
    void add() {       
        atomicInteger.getAndIncrement();
    }
    public static void main(String[] args) {
        Test01 data = new Test01();
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    data.add();
                }
            }).start();
            new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    data.add();
                }
            }).start();
        }
        while (Thread.activeCount() > 2) {
            Thread.yield();//线程等待
        }
        System.out.println(data.atomicInteger);
    }
}

结果:

3 有序性(Ordering)

计算机在执行程序时,为了提高性能,编译器和处理器常常会做指令重排,一把分为以下几种

单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致.

处理器在进行重新排序是必须要考虑指令之间的数据依赖性

多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程使用的变量能否保持一致性是无法确定的,结果无法预测

3.1 指令重排情况

情况一:

public void mySort(){
    int x=11;//语句1
    int y=12;//语句2
    x=x+5;//语句3
    y=x*x;//语句4
}

可能情况:

1234

2134

1324

问题:

请问语句4 可以重排后变成第一条码?

存在数据的依赖性 没办法排到第一个

情况二:

public class Test{
    int i = 0;
    boolean isOk = false;
    public void methodA(){
        i = 1;  //语句1
        isOk = true;  //语句2 
    }
    public void methodB(){
        if(isOk){
            i++;
            System.out.print(i);
        }
    }  
}

这种情况下,由于语句1 和语句2 没有依赖性,如果语句2在语句1之前执行,则会导致methodB方法提前执行

3.2 禁止指令重排

volatile实现禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象,先了解一个概念,内存屏障(Memory Barrier)又称内存栅栏,是一个CPU指令,它的作用有两个:

  • 一是保证特定操作的执行顺序,
  • 二是保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)。

由于编译器和处理器都能执行指令重排优化。如果在指令间插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序,也就是说通过插久内存屏障禁止在内存屏障前后的指令执行重排序优化。内存屏障另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。



相关文章
|
6月前
|
缓存 Java C++
JVM(三): JMM
JVM(三): JMM
|
6月前
|
存储 缓存 Java
金石原创 |【JVM盲点补漏系列】「并发编程的难题和挑战」深入理解JMM及JVM内存模型知识体系机制(1)
金石原创 |【JVM盲点补漏系列】「并发编程的难题和挑战」深入理解JMM及JVM内存模型知识体系机制(1)
82 1
|
5月前
|
存储 安全 Java
深入理解Java内存模型(JMM)与虚拟机的内存结构(JVM)
深入理解Java内存模型(JMM)与虚拟机的内存结构(JVM)
|
6月前
|
存储 缓存 Java
【深入浅出JVM原理及调优】「搭建理论知识框架」全方位带你认识和了解JMM并发模型的基本原理
每位Java开发者都了解到Java字节码是在Java运行时环境(JRE)上执行的。JRE包含了最为关键的组成部分:Java虚拟机(JVM),它负责分析和执行Java字节码。通常情况下,大多数Java开发者无需深入了解虚拟机的内部运行原理。即使对虚拟机的运行机制不甚了解,也不会对开发工作产生太多影响。然而,对JVM有一定了解的话,将更有助于深入理解Java语言,并解决一些看似困难的问题。
86 4
【深入浅出JVM原理及调优】「搭建理论知识框架」全方位带你认识和了解JMM并发模型的基本原理
|
存储 缓存 安全
【JVM原理探索】你最应该阅读的JMM学习指南 | Java开发实战
【JVM原理探索】你最应该阅读的JMM学习指南 | Java开发实战
164 0
【JVM原理探索】你最应该阅读的JMM学习指南 | Java开发实战
|
存储 缓存 安全
JVM技术之旅-带你吃透JMM知识体系
JVM技术之旅-带你吃透JMM知识体系
144 0
JVM技术之旅-带你吃透JMM知识体系
|
存储 安全 Java
JVM技术之旅-了解介绍JMM模型
JVM技术之旅-了解介绍JMM模型
107 0
JVM技术之旅-了解介绍JMM模型
|
存储 缓存 Java
JVM还没学明白,又来了JMM!
JVM还没学明白,又来了JMM!
|
算法 安全 Java
深入理解JVM-内存模型(jmm)和GC的几个疑问
深入理解JVM-内存模型(jmm)和GC
230 0
深入理解JVM-内存模型(jmm)和GC的几个疑问
|
1月前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
37 4