美文网首页
Java并发基础之内存模型

Java并发基础之内存模型

作者: 仰望forward | 来源:发表于2019-11-15 11:43 被阅读0次

并发三问题

  • 重排序
  • 内存可见性
  • 原子性

1. 重排序

public class Test {

    private static int x = 0, y = 0;
    private static int a = 0, b =0;

    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        for(;;) {
            i++;
            x = 0; y = 0;
            a = 0; b = 0;
            CountDownLatch latch = new CountDownLatch(1);

            Thread one = new Thread(() -> {
                try {
                    latch.await();
                } catch (InterruptedException e) {
                }
                a = 1;
                x = b;
            });

            Thread other = new Thread(() -> {
                try {
                    latch.await();
                } catch (InterruptedException e) {
                }
                b = 1;
                y = a;
            });
            one.start();other.start();
            latch.countDown();
            one.join();other.join();

            String result = "第" + i + "次 (" + x + "," + y + ")";
            if(x == 0 && y == 0) {
                System.err.println(result);
                break;
            } else {
                System.out.println(result);
            }
        }
    }
}

观察代码可以发现,如果没有意外情况发生的话,在上下两个线程中,出现的结果应该下面三种情况

x= 0 ,y = 1; </p>
x= 1 ,y = 1; </p>
x= 1 ,y = 0;

但是在实际运行过程中,却最终有概率出现 x=0,y=0的情况。这种情况发生的原因就是出现了重排序。

重排序由一下几种机制引起:</p>

  1. 编译器优化:对于没有数据依赖关系的操作,编译器在编译的过程中会进行一定程度的重排。</p>
    可以看到线程1中的代码,编译器是可以将a=1和x=b换一下顺序的,因为它们之间没有数据依赖关系,同理,线程2也一样,那就不难得到x==y==0的结果了。</p>
  2. 指令重排序:CPU优化行为,也是会对不存在数据依赖关系的指令进行一定程度的重排</p>
    这个和编译器优化差不多,就算编译器不发生重排,CPU也可以对指令进行重排。</p>
  3. 内存系统重排序:内存系统没有重排序,但是由于缓存的存在,使得程序整体上会表现出乱序的行为。</p>
    假设不发生编译器重排和指令重排,线程1修改了a的值,但是修改以后,a的值可能还没写回到主内存中,那么线程2得到a==0就是很自然的事了。同理,线程2对于b的赋值操作也可能没有及时刷新到主存中。

2.内存可见性

线程间的对于共享变量的可见性问题不是直接由多核引起的,而是由多缓存引起的。如果每个核心共享同一个缓存,那么也就不存在内存可见性问题了。 </p>
现代多核CPU中每个核心拥有自己的一级缓存或一级缓存加上二级缓存等,问题就发生在每个核心的独占缓存上。每个核心都将会自己需要的数据读到独占缓存中,数据修改后也是写入到缓存中,然后等待刷入到主存中。所以会导致有些核心读取的值是一个过期的值。</p>
在JMM中,抽象了主内存和本地内存的概念。</p>
所有的共享变量存在于主内存中,每个线程有自己的本地内存,线程读写共享数据也是通过本地内存交换,所以可见性问题依然存在。这里说的本地缓存并不是真的是一块给每个线程分配的内存,而是JMM的一个抽象,是对于寄存器、一级缓存、二级缓存等的抽象。

3.原子性

对于long和double,它们的值都需要占用64位的内存空间,Java编程语言规范中提到,对于64位的值的写入,可以分为两个32位的操作进行写入。本来一个整体的赋值操作,将被拆分为低32位赋值和高32位赋值两个操作,中间如果发生了其他线程对于这个值的读操作,必然会读到一个奇怪的值。</p>
这个时候我们需要使用volatile关键字进行控制了,JMM规定了对于volatile long 和volatile double,JVM需要保证写入操作的原子性。</p>
另外,对于引用的读写操作始终是原子的,不管是32位的机器还是64位的机器。</p>
Java编程规范同样提到,鼓励JVM的开发者能保证64位值操作的原子性,也鼓励使用尽量使用volatile或使用正确的同步方式。关键词是“鼓励”。</p>
在64位JVM中,不加volatile也是可以的,同样能保证对于long和double写操作的原子性。

Java对于并发的规范约束

Synchronization Order

  • 对于监视器m的解锁与所有后续操作对于m的加锁同步
  • 对于volatile变量v的写入,与所有其他线程后续对v的读同步
  • 启动线程的操作与线程职工的第一个操作同步
  • 对于每个属性写入默认值(0,false,null)与每个线程对其进行的操作同步。

Happens-before Order

两个操作可以用Happens-before来确定它们的执行顺序,如果一个操作happens-before于另一个操作,那么我们说第一个操作对于第二个操作是可见的。</p>
如果我们分别有操作X和操作Y,我们写成hb(x,y),来表示 x happens-before y 。</p>

  • 如果操作x和操作y是同一个线程的两个操作,并且在代码上操作x先于操作y出现,那么有hb(x,y)。
  • 对象构造方法的最后一行指令happens-before于finalize()方法的第一行指令。
  • 对于操作x与随后的操作y构成同步,那么hb(x,y)
  • hb(x,y)和hb(y,z),那么可以推断出hb(x,z)

这里需要说明的是:hb(x,y),并不是说x操作一定要在y操作之前被执行,而是说x的执行结果对于y是可见的,只要满足可见性,发生了重排序也是可以的。

synchronized关键字

一个线程获取到锁以后才能进入synchronized控制的代码块,一旦进入代码块,首先,该线程对于共享变量的缓存就会失效,因此synchornized代码块中对于共享变量的读取需要从主内存中重新获取,也就能获取到最新的值。</p>
退出代码块的时候,会将该线程写缓冲区的数据刷到主内存中。

相关文章

  • Java基础之内存模型

    Java基础之内存模型 目录 Java内存模型简单介绍 JVM介绍 存储方式 并发原因 Java内存模型与系统内存...

  • 高并发Java

    高并发Java(1):前言 高并发Java(2):多线程基础 高并发Java(3):Java内存模型和线程安全 高...

  • Java内存模型

    Java内存模型的基础 本文是《java并发编程的艺术》一书的学习笔记 1.Java内存模型的抽象结构 1.Jav...

  • java内存模型

    java内存模型基础 并发编程,两个关键问题:线程通信和线程同步通信机制:共享内存和消息传递 java并发采用共享...

  • Java并发编程脑图

    01-Java内存模型 02-并发基础 03-锁 04-并发工具类 05-其他 06-Java并发集合 07-at...

  • 深入分析 Java 内存模型与应用

    深入分析 Java 内存模型,奠定坚实的并发编程基础。欢迎扫码参与。

  • java并发编程系列-volatile内存实现和原理

    前面的博文说了java的内存模型,介绍了java内存模型的基础,此篇文章来说一下volatile关键字,这个在并发...

  • JAVA内存模型笔记(JMM)

    JAVA内存模型 这里做的笔记是结合JVM中的java内存模型 和java并发编程艺术中讲的java内存模型 再结...

  • 2.深入volatile

    1.并发基础 2.java内存模型&jvm内存模型 上图可以清除看出,每个工作线程都有独立的内存空间 3.特性 可...

  • 并发编程03-Java内存模型01

    Java内存模型基础并发编程模型的两个关键问题线程之间的通信线程之间的同步Java内存模型的抽象结构从源代码到指令...

网友评论

      本文标题:Java并发基础之内存模型

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