连咸鱼都能学会的Java多线程中的内存模型

hello,这期博客我们来说说Java多线程中的内存模型

首先,我们用一个图来简单描述下线程与主内存的关系怎么样的:

《连咸鱼都能学会的Java多线程中的内存模型》

嗯。。。大概是这个样子吧

在JVM中规定了所有变量都存储到主内存中,然后每条线程有自己的工作内存,线程中的工作内存保存了该线程所需要用到的变量的副本(从主内存拷贝下来的),线程在cpu上运行的时候是对自己的工作线程中的数据进行读写操作,运行结束后才把数据同步到主内存中。

那么有一个很重要的问题:

当多个线程对同一内存区域进行读写操作,以哪个线程的值作为结果写入主内存的变量中呢?

JVM中就需要线程同步机制来达到多线程对同一内存区域的读写控制。

还有,Java编译器为了提高性能,采取了指令重排序,若多个线程都有语句对同一内存区域进行操作的话,有可能因为指令重排序而导致结果不符预料,

因此也需要线程同步机制来达到多线程对同一内存区域的读写控制。

主内存与工作内存的数据交互:

JVM中定义了8种操作来完成主内存与线程工作内存的数据交互:

1. lock:把主内存变量标识为一条线程独占,此时不允许其他线程对此变量进行读写

2. unlock:解锁一个主内存变量

3. read:把一个主内存变量值读入到线程的工作内存,强调的是读入过程

4. load:把read到变量值保存到线程工作内存作为变量副本,强调的事读入的值的保存过程

5. use:线程执行期间,把工作内存中的变量值传给字节码的执行引擎

6. assign(赋值):字节码执行引擎把运算结果传回工作内存,赋值给工作内存中的变量

7. store:把工作内存中的变量值传到主内存中,强调传送的过程

8. write:把store传送进来的变量值写入主内存的变量中,强调保存的过程

JVM要求以上的8个操作都是原子性的,即对数据的读写操作具有原子性

但也有例外:如long,double的非原子性协定:这两个64位类型的数据的读、写操作各需两次进行,一次读写32位,这两次读、写是不保证原子性的

原子性:基本数据类型的读写操作是原子性的,更大范围的代码块的原子性可以用lock, unlock操作实现(上锁之后只有一个线程来执行,所以不会被其他线程打断原子操作),表现到代码层面就是使用synchronized同步

可见性:当一个线程修改了被多线程共享的一个主内存变量值时,其他线程能立刻知道这个修改

从上面可以知道,JVM是通过工作内存的变量值变化后,把新值同步到主内存中,然后其他线程从主内存读取这个新值来实现可见性。

这里有个区别:普通变量的值变化后,不一定会立刻同步到主内存,而是会等线程执行完或有一段时间后才会同步,而且同步回主内存后,其他线程的工作内存也不一定会立刻读取新值。 而被volatile关键字修饰的变量,一旦工作内存中被修改,则立刻同步回主内存,并且其他使用了这个变量的线程的工作内存会立刻从主内存读取新值。

而synchronized关键字修饰的变量由于一次只能有一个线程能使用,故一次也只能有一个工作线程读写它,所以也能“纵向”地实现可见性

有序性:多线程之间对共享数据的操作的有序性,可以通过volatile和synchroized关键字来保证,volatile关键字禁止了指令重排序,而synchroized关键字规定了多个线程每次只能有一个线程对共享数据进行操作

关于volatile关键字:

一个volatile变量具有两种特性:可见性,禁止指令重排序,但是volatile不具备原子性,原因是volatile变量的值可以被多线程交替修改,而修改包括了read, load, use, store, write等过程,这些过程不能保证原子执行的。

可见性:被volatile关键字修改的变量,一旦在工作内存中被修改,则会立刻同步回主内存,并且其他使用这个变量的线程的工作内存会立刻从主内存读取新值

禁止指令重排序:volatile变量在赋值后会创建一个内存屏障,指令重排序时,位于后面的指令不能排列到内存屏障之前。

转载于:

https://www.cnblogs.com/ygj0930/p/6536131.html

    原文作者:java内存模型
    原文地址: https://blog.csdn.net/a724826175/article/details/78524023
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞