美文网首页
Java内存模型学习笔记

Java内存模型学习笔记

作者: ssochi | 来源:发表于2019-12-14 17:34 被阅读0次

目标

Java内存模型的目标是定义程序中各个变量的访问规则.即虚拟机对变量在内存中存取的规则.

目的

Java内存模型的目的是屏蔽各种硬件和操作系统的内存访问差异,使得Java在各个平台下能达到一致的内存访问效果.

主内存与工作内存

  • Java内存模型将规定所有变量存储于主内存,但每条线程拥有一个自己独立的工作内存.
  • 工作内存中保留了该线程对于主线程中用到的变量的拷贝.(这里需要强调一点,并不是用到一个对象,就要将这个对象全部拷贝到工作内存中来,工作内存拷贝的是变量,而这些变量一遍只是一个对象的一部分).
  • 工作内存彼此独立,无法相互访问

Java线程 <===>工作内存<===>Save&Load<===>主内存

进一步理解,主内存可以理解为Java堆,而工作内存可以理解为栈,但工作内存还有一部分属于高速缓存.

内存间的交互操作

共有八种操作:lock,unlock,read,load,use,assign,store,write
一下是对这些操作的限制:

  • read之后必须要有load,store之后必须要有write
  • assign之后必须要有store(原话是一个线程不允许丢弃最近的assign操作,这是笔者自己的理解)
  • 不允许没有assign却调用store
  • 如果一个变量没有被assign和load,那么就不能use
  • 一个变量同时只允许一个线程对其lock,但是一个线程可以对其多次lock,但之后得进行多次unlock才能解锁
  • 对一个变量执行lock操作,首先会清空该变量在工作空间中的值,在使用到这个变量前,需要对它重新调用load或assign操作
  • unlock前必须lock
  • 在对一个变量执行unlock操作前,需要调用store,write将它的值同步到主内存中

volatile的特殊规则

可见性

在volatile标记的变量使用前必会调用read->load->use
在volatile标记的变量赋值后,会调用assign->store->write
也就是说,被volatile标记的变量标记的对象会及时的刷新主内存中它的值,并在工作内存中使用主内存中最新的值.
但由于从use到变量真正的使用往往不是原子性操作,因此单纯的使用volatile也往往不是线程安全的

有序性

在计算机执行指令时,会对指令就行重排序,而volatile关键字能阻止这个行为。在volatile之前的操作不会再它之后执行,而在volatile之后的操作不会在它之前执行。典型的例子就是双重判断的单例模式,对instance加volatile关键字的目的就是防止对象初始化和对象赋值的重排序。

final

final与volatile相同也具备可见性和有序性。

Happens-Before规则

  • 单个线程内程序顺序执行,重排序线程内不可知。
  • 解锁操作先于加锁操作
  • volatile的写入操作先于读取操作
  • 线程启动先于线程运行
  • 线程运行先于线程结束
  • 对象构造函数先于对象的终结函数
  • 传递性

举个例子,有一下两个线程:
线程一:

    y = 1
    lock M
    x = 1
    unlockM

线程二:

    lock M
    i = x
    unlockM
    j = y

如果线程1的unlockM先于线程2的lockM
那么我们可以推到出线程1的所有操作先于线程2;
但是如果去掉lock和unlock代码:

    y = 1
    x = 1

线程二:

    i = x
    j = y

如果x = 1 先于 i = x;我们并不能推出y = 1 先于 j = y;

再举个简单的例子,看如下代码:

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (!shutdown);
                System.out.println(count);
            }
        }).start();

        Thread.sleep(100);
        for (int i = 0; i < 500; i++) {
            count++;
        }
        shutdown = true;

如果shutdown不为volatile,那么大概率这个进程永远也无法结束,因为shutdown不具备可见性。即使这个进程成功停止了,输出的count也是不确定的。
但如果shutdown是volatile,那么输出的count必定等于500,因为我们能确定对shutdown的写操作一定先于对shutdown的读操作,那么在对shutdown写操作之前的所有操作也应该先于对shutdown读操作之后的所有操作,因此,对count的写操作就会先于对count的读操作。

相关文章

网友评论

      本文标题:Java内存模型学习笔记

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