• Java内存模型


    硬件设备

      处理器的运算速度很快,但是处理器又要和内存打交道,读取运算数据、存储运算结果的过程很缓慢,此时需要一个高速缓存,运算时将数据复制到缓存中,运算完成后将结果同步会内存,处理器无需等待内存读写加快速度。但是会产生缓存一致性问题,需要依赖一定的协议。“内存模型”可以理解为在特定的协议下,对特定的内存或高速缓存进行读写访问的过程抽象。 

      

    Java内存模型

    主内存和工作内存

      所有的变量(实例字段、静态字段、构成数组对象的元素)都存储在主内存中(与硬件主内存类比,线程共享,主要对应Java堆中的实例数据)。每条线程都有自己的工作内存(与硬件高速缓存类比,线程不共享,主要对应虚拟机栈中的部分数据),线程的工作内存保存了该线程使用到的变量的主内存副本拷贝,线程对变量的操作(读写、赋值)都在工作内存中进行,不能直接操作主内存的变量,不同线程之间不能互相访问对方工作内存中的变量,只能通过主内存来访问。

      

    主内存与工作内存之间的交互操作

      lock(锁定):作用于主内存的变量,将主内存中的一个变量标志为一条线程独占状态。

      unlock(解锁):作用于主内存的变量,将标识为线程独占状态的变量释放出啦,之后才能被其他线程 lock。

      read(读取):作用于主内存的变量,将一个变量的值传输到工作内存中,以便 load 操作。

      load(载入):作用于工作内存的变量,把 read 操作得到的变量值放入到变量副本中。

      use(使用):作用于工作内存的变量,将变量的值传递给执行引擎,虚拟机遇到使用变量的值的字节码指令时执行该操作。

      assign(赋值):作用于工作内存的变量,把从执行引擎获得的值赋值给工作内存的变量,虚拟机遇到给变量赋值的字节码指令时执行该操作。

      store(存储):作用于工作内存的变量,将变量传送到主内存中,以便 write 操作。

      write(写入):作用于主内存的变量,把 store 操作得到的变量值放入到主内存的变量中。

    volatile型变量

      当一个变量用volatile修饰时,该变量具备两个特性:一是该变量对所有线程可见,二是禁止指令重排序优化(有序性)。

    线程可见  

      对所有线程可见是指一条线程修改了该变量的值,新值对于其他线程可以立即得知。普通变量需要通过主内存和工作内存交互才可见。

      volatile修饰的变量的运算在并发下可能是不安全的。

    package com.wjz.demo;
    
    public class VolatileDemo {
        public static volatile int val = 0;
        public static final int THREAD_COUNT = 20;
        public static void add() {
            val++;
        }
        public static void main(String[] args) {
            Thread[] ts = new Thread[THREAD_COUNT];
            for (int i = 0; i < THREAD_COUNT; i++) {
                ts[i] = new Thread(new Runnable() {
                    public void run() {
                        for (int j = 0; j < 10000; j++) {
                            add();
                        }
                    }
                });
                ts[i].start();
            }
            while (Thread.activeCount() > 1) {
                Thread.yield();
                System.out.println(val);
            }
        }
    }

      运行结果理论上是200000,但是结果总是小于该数值。

      javap查看字节码命令(add方法中的自增运算部分) 

    0: getstatic     #2                  // Field val:I
    3: iconst_1
    4: iadd
    5: putstatic     #2                  // Field val:I

      首先getstatic指令将val数值载入操作数栈顶,volatile保证了此时变量值的一致性,执行iconst_1和iadd指令时,其他线程可能已经将val数值增大,此时的val数值是过期的,putstatic指令可能将较小的val数值同步回主内存中了。

      volatile型变量的使用场景,可以作为其他线程运行停止的开关。

       public static volatile boolean running = true;
        public void shutdown() {
            running = false;
        }
        public void dowork () {
            while (running) {
                // do something
            }
        }

    禁止指令重排序优化

      volatile型变量赋值后多执行一个空操作(store和write),该操作相当于内存屏障,将修改同步到主内存。

    long、double型变量

      虚拟机会将该类型数据的读写操作作为原子操作对待,一般该类型变量不需volatile修饰。

    原子性、可见性和有序性

      原子性: 

  • 相关阅读:
    JS jQuery显示隐藏div的几种方法
    PHP 二维数组去重(保留指定键值的同时去除重复的项)
    Java面试题解析(一)
    Java :面向对象
    使用 Spring Framework 时常犯的十大错误
    Spring Boot 面试的十个问题
    《深入理解 Java 内存模型》读书笔记
    Spring Boot 2.0 迁移指南
    MaidSafe区块链项目白皮书解读
    20190712共学问题归纳
  • 原文地址:https://www.cnblogs.com/BINGJJFLY/p/7700845.html
Copyright © 2020-2023  润新知