一:现代计算机的高速缓存

在计算机组成原理中讲到,现代计算机为了匹配 计算机存储设备的读写速度 与  处理器运算速度,在CPU和内存设备之间加入了一个名为Cache的高速缓存设备来作为缓冲:将运算需要用到的数据从内存复制到cache中,CPU可以在运算期间对cache进行高速的读写操作,运算结束后在从cache把数据同步回内存。

Cache引出了一个新问题:缓存一致性。每个处理器有自己的cache,而他们又共享一个主内存。当多个处理器的运算任务都设计同一内存区域时,将会导致各自的缓存数据不一致。那么,在各自运算结束后,以谁的缓存数据为准同步回主存呢?此时,就需要 缓存一致性协议 来制定多个处理器对统一内存区域的读写操作了。

除了Cache,为了更好地利用处理器资源,处理器还会对代码进行 乱序执行 ,在最后才把结果重组使得结果与顺序执行一致。

产生上面这种cache区的问题就是cpu和内存之间的读取速度还不够快, 所以需要加入cache缓冲进行更加高速的读取, 但是有引出了新的问题就是, 内存中的变量和cache中的变量同步问题, 可能出现不一致的情况。

这样就引入了 "缓存一致性协议" 用来解决这个问题

二:Java内存模型的类比

类比与现代计算机的主存与cache,JVM中规定了 所有变量都存储在主内存中(类比计算机的主存),然后每条线程有自己的工作内存(类比每个处理器的cache)。线程的工作内存中保存了该线程需要用到的变量的拷贝值,线程在CPU上运行时都是对自己工作线程中的数据进行读写操作,运行结束之后才把数据同步化主内存中。那么类比于计算机使用 缓存一致性协议 解决缓存一致性问题,JVM中就需要线程同步机制来达到多线程对同一内存区域的读写控制了。

此外,Java编译器为了提高性能,采取了 指令重排序(类比计算机的 乱序执行),若多个线程都有语句对同一内存区域进行操作的话,有可能因为指令重排序而导致结果不符预料。因此,也需要线程同步机制来达到多线程对同一内存区域的读写控制

应该计算机CPU的cache缓存的机制比较好, 所以大家都抄过去用, 所以jvm也使用了这个机制, 导致现在线程也出现了自己的cache, 所以需要使用线程同步机制来解决这个问题

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

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操作来实现(上锁后就只有一个线程来执行了,所以不会被其他线程打断原子操作),表现到代码层面就是使用syncrhoized同步块。

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

我们在上面可以知道,JVM是通过工作内存中的变量值变化后,把新值同步会主内存,然后其他线程从主内存读取这个新值来实现可见性的。这里有个区别:普通变量的值变化后,不一定会立刻同步会主内存,而是会等线程执行完或者一段时间后才同步会,而且同步回主内存后,其他线程的工作内存也不一定会立刻读取新值。而被volatile关键字修饰的变量,一旦在工作内存中被修改,则立刻同步回主内存,并且其他使用了这个变量的线程的工作内存会立刻从主内存读取新值。而syncrhoized关键字修饰的变量由于一次只能有一个线程能使用,故一次也只能有一个工作线程读写它,所以也能“纵向”地实现可见性。

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

五:volatile关键字

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

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

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

  1. public class Demo {

    public static void main(String[] args) {
    ThreadDemo threadDemo = new ThreadDemo();
    new Thread(threadDemo).start();
    while (true) {
    // 这里必须是主线程先执行才行, 否则子线程先执行的话, 那么会让jvm有时间去将线程中缓存的的flag变量的值同步到主内存, 所以我们一定要让主线程陷入死循环, 没有多余的时间片去发现flag变量被改变
    if (threadDemo.isFlag()) {
    System.out.println("---------------------------------------------------------------");
    break;
    }
    }
    }

    }

    class ThreadDemo implements Runnable {
    private volatile boolean flag = false;

    @Override
    public void run() {
    // 为了防止主线程
    try {
    Thread.sleep(200);
    } catch (InterruptedException e) {
    }
    flag = true;
    System.out.println("flag = " + isFlag());
    }

    public boolean isFlag() {
    return flag;
    }
    }




















[心得笔记]Java多线程中的内存模型的更多相关文章

  1. Java多线程中的内存模型

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6536131.html  一:现代计算机的高速缓存 在计算机组成原理中讲到,现代计算机为了匹配 计算机存储设备的 ...

  2. java多线程中的三种特性

    java多线程中的三种特性 原子性(Atomicity) 原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行. 如果一个操作时原子性的,那么多线程并 ...

  3. Java虚拟机学习 - 体系结构 内存模型(转载)

    一:Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代” .“非堆”,  它用于存储虚拟机加载的类信息.常量.静态变量.是各个线程共享的内存区域.默认最小值为16MB, ...

  4. (转载)JVM中的内存模型与垃圾回收

    转载自微信公众号:Java高级架构(Java-jiagou)-----看完这篇文章,我奶奶都知道JVM中的内存模型与垃圾回收了! 六.内存模型 6.1  内存模型与运行时数据区 Java虚拟机在执行J ...

  5. 【java虚拟机】jvm内存模型

    作者:pengjunlee原文链接:https://blog.csdn.net/pengjunlee/article/details/71909239 目录 一.运行时数据区域 1.程序计数器 2.J ...

  6. Java虚拟机学习 - 体系结构 内存模型

    一:Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代” .“非堆”, 它用于存储虚拟机加载的类信息.常量.静态变量.是各个线程共享的内存区域.默认最小值为16MB,最 ...

  7. Java虚拟机中的内存分配

    java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途以及创建和销毁的时间. 栈:存放的是局部变量,包括:1.用来保存基本数据类型的值:2.保存类 ...

  8. Java虚拟机学习 - 体系结构 内存模型(1)

    一:Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代" ."非堆",  它用于存储虚拟机加载的类信息.常量.静态变量.是各个线程共享的内 ...

  9. Java:JVM的内存模型

    JVM内存模型 JVM内存模型可以分为两个部分,如下图所示,堆和方法区是所有线程共有的,而虚拟机栈,本地方法栈和程序计数器则是线程私有的.   1. 堆(Heap) 堆内存是所有线程共有的,可以分为两 ...

随机推荐

  1. Mybatis 逆向工程使用(Day_17)

    有三个方法可以解决所有的问题:接受,改变,离开.不能接受那就改变,不能改变,那就离开. [何为逆向工程?] 众 所 周 知 ,正向工程就是通过JAVA类的设置来生成对应的数据库表. MyBatis逆向 ...

  2. Spring5.0源码学习系列之事务管理概述

    Spring5.0源码学习系列之事务管理概述(十一),在学习事务管理的源码之前,需要对事务的基本理论比较熟悉,所以本章节会对事务管理的基本理论进行描述 1.什么是事务? 事务就是一组原子性的SQL操作 ...

  3. pytest基础简介及实践举例

    一.pytest简介 pytest 是 python 的第三方单元测试框架,比自带的 unittest 更简洁和高效,同时兼容 unittest 框架.它还有如下优点: 1.简单灵活,容易上手,文档丰 ...

  4. python_xlutils : python利用xlutils修改表格内容

    一.xlutils是什么 是一个提供了许多操作修改excel文件方法的库: 属于python的第三方模块 xlrd库用于读取excel文件中的数据,xlwt库用于将数据写入excel文件,修改用xlu ...

  5. selenium-python元素定位技巧(二)

    在python-selenium元素定位中,有很多小技巧,在此记录总结  技巧一.有关联关系的元素有交集关系时,必须添加固定等待,整体调试时候也适当增加固定等待,保持代码运行稳定性.下拉的时候选择下拉 ...

  6. P5837 [USACO19DEC]Milk Pumping G

    题目描述 Farmer John 最近为了扩张他的牛奶产业帝国而收购了一个新的农场.这一新的农场通过一个管道网络与附近的小镇相连,FJ 想要找出其中最合适的一组管道,将其购买并用来将牛奶从农场输送到小 ...

  7. Vue.js源码解析-Vue初始化流程

    目录 前言 1. 初始化流程概述图.代码流程图 1.1 初始化流程概述 1.2 初始化代码执行流程图 2. 初始化相关代码分析 2.1 initGlobalAPI(Vue) 初始化Vue的全局静态AP ...

  8. mapboxgl 互联网地图纠偏插件(一)

    之前写过一个 leaflet 互联网地图纠偏插件,引用插件后一行代码都不用写,就能解决国内互联网地图瓦片的偏移问题. 最近想对 mapboxgl 也写一个这样的插件. 原因是自己发布的OSM矢量瓦片地 ...

  9. android动画系列

    Android 属性动画(Property Animation) 完全解析 (上 动画系列 - 传统View动画与Property动画基础及比较 [Android 基础]Animation 动画介绍和 ...

  10. SQLLite数据库

    SQLite数据库简介 一个小时内学习SQLite数据库 SQLite 教程 创建表: 1 sqlite> CREATE TABLE person (id INTEGER PRIMARY KEY ...