什么是内存模型?

内存模型: 可以理解为,在特定的操作协议下,对特点的内存高速缓存进行读写访问过程的抽象

什么是高速缓存?

在运算时,将需要使用到的数据从内存复制到缓存(Cache)中,以此让计算能更快的进行,计算结束后再从缓存同步回内存中,这样就无需频繁的等待缓慢的内存读写

缓存一致性

缓存一致性: 在多处理器系统中,每个处理器都有自己的高速缓存,而它们又共享同一主内存。
在这里插入图片描述

Java 内存模型

主内存

主内存是虚拟机内存的一部分。

Java 内存模型规定所的变量都存储在主内存中。

变量数据: 最根源的数据,线程不能直接操作。

工作内存

工作内存中保存了被该线程使用到的变量主内存副本拷贝,线程对变量的所有操作(读取,赋值等),都必须在工作内存中进行,不能直接读写主内存中的数据。不同线程间也无法直接访问对方工作线程中的变量

变量数据: 从主内存中拷贝来的副本,其他线程无法访问,操作结束后需写回主内存。
在这里插入图片描述

内存间的交互操作

虚拟机实现时,必须保证下面提及的每一种操作都是原子的不可再分的

  • lock(锁定): 作用于主内存的变量,把变量标记为某个线程独占。
  • unlock(解锁): 作用于主内存的变量,释放一个锁定的变量。
  • read(读取): 作用于主内存的变量,把一个变量的值从主内存传输到工作内存中,以便后续 load 动作使用。
  • load(载入): 作用于工作内存的变量,将 read 过来的值放入工作内存变量副本中。
  • use(使用): 作用于工作内存的变量,把 工作内存 中存在的一个变量传递给执行引擎,每当虚拟机遇到一个需要使用变量的值时就会执行这个操作。
  • assign(赋值): 作用于工作内存的变量,把从执行引擎接收到的值,赋值给工作内存中的一个变量,当虚拟机遇到赋值操作时候执行操作。
  • store(存储): 作用于工作内存的变量,把工作内存中的一个变量传递给主内存,一遍后续 write 动作使用。
  • write(写入): 作用于主内存的变量,把 store 操作的值放入主内存的变量中。

将变量从主内存复制到工作内存: 顺序执行 readload,只需按顺序即可,中间可插入其他指令。

从工作内存写回主内存: 顺序执行 sorewrite,只需按顺序即可,中间可插入其他指令。

volatile 关键字

对所有线程可见性

特性: 保证此变量对所有线程的可见性,当一条线程修改了这个变量值,其他线程会立即收到通知。

volatile 关键字保证线程可见,不代表线程安全:
以下为 race ++ 的反编译结果
在这里插入图片描述
volatile 关键字 保证了线程可见性,因此只要去读数据,就可以拿到正确的数据

而这里的读操作,只有 getstatic 字节码指令,将数据读入栈顶(每个线程各自拥有,对外部可见)

而后续的不管是递增 iadd 还是回写回主内存 putstatic 指令之前都没有再去读数据

因此如果这期间如果数据发生了变更,在栈顶的数据就成了旧数据回写回主内存就会产生覆盖线程不安全
在这里插入图片描述

禁止指令重排序

普通变量仅仅会保证在该方法的执行过程中,所有依赖赋值结果的地方都能获取到正确的结果,而不能保证变量赋值操作的顺序与程序代码中的执行顺序一致

这么做的目的是一种机器级的优化操作,使得某个部分的汇编代码被提前执行

volatile 可以避免这种的优化操作。

内存屏障:
volatile 是通过 内存屏障 来防止指令重排序的,反编译后即下图红色框框中的部分。

作用: 防止 内存屏障 后面的指令排序到 内存屏障 之前。
在这里插入图片描述

Java 内存模型的特征

Java 内存模型是围绕 并发过程中 如何处理 原子性可见性有序性 3 个特征来建立的。

原子性

直接 保证原子性的操作 包括:readloadassignusestorewrite

基本数据类型的访问读写都是具备原子性的
例外的只有 longdouble,具有非原子协定,但是各大虚拟机实现时,都把他们的读写操作作为原子操作来对待,因此知道下就好了,不需要太在意。

为了保证原子性,虚拟机提供字节码 monitorentermonitorexit 来操作 lockunlock,反应到代码层面就是 synchronized 关键字,因此 synchronized 块的操作也具有原子性。

可见性

可见性: 可见性是指,当一个线程修改了共享变量的值,其他变量能立即得知这个修改。

保证可见性的关键字: volatilesynchronizedfinal

有序性

有序性: 如果在本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无需的。前半句是指 “线程内表现为串行的语义”,后半句是指 “指令重排序” 现象和 “工作内存和主内存同步延迟” 现象。

防止指令重排序关键字: volatilesynchronized

synchronized 缺点

synchronized 同时满足 3 大特性,看起来十分 “万能”,但过度滥用回导致性能问题。

先行发生原则

即多线程同时操作一个变量导致值不正确的线程不安全问题。

“天然的” 先行发生关系

即不需要同步器,在代码中能直接使用

  • 程序次序规则:一个线程内,按照程序代码的顺序,写在前面的代码比写在后面的代码先执行
  • 管程锁定规则: 同一个锁,一个 unlock 操作先行发生于后面对同一个锁的 lock 操作。不解锁就没法再锁定。
  • volatile 变量规则: 对一个 volatile 变量的写操作优先于后面这个变量的读操作,“后面” 是指时间上的先后顺序。
  • 线程启动规则: Thread 对象的 start() 方法先行发生于此线程的每个动作。不启动线程,其他动作无法进行。
  • 线程终止规则: 线程中所有操作都优先于此线程的终止检查,可通过 Thread.join() 方法结束Thread.isAlive() 返回值 等手段检查到线程已经终止执行。
  • 线程中断规则: 对线程 interrupt() 方法的调用先行发生于被中断线程的代码检查到终端事件的发生,可通过 Thread.interrupt() 方法检查到是否有中断发生。
  • 对象终结规则: 一个对象的初始化完成(构造函数执行结束),先行发生于它的 finalize() 方法的发送
  • 传递性: 操作 A 优先于操作 B,操作 B 优先于操作 C,操作 A 就必然优先于 操作 C

Java 线程状态

  1. 新建(New): 创建后 尚未启动 的线程状态
  2. 运行(Runable): 包含了操作系统线程状态中的 RunableReady 状态,处于此状态下的线程可能处于 正在执行 或者 等待 CPU 分配执行事件
  3. 无期限等待(Waiting): 此状态的线程 不会被分配执行事件,需要 等待被其他线程唤醒。以下方法会让线程进入 Waiting
    没有设置 Timeout 参数的 Object.wait() 方法。
    没有设置 Timeout 参数的 Object.join() 方法。
    LockSupport.park() 方法。
  4. 限期等待(Time Waiting): 此状态下线程不会被分配 CPU 执行时间,不过无需其他线程显式的唤醒,在一定时间后由系统自动唤醒。以下方法会让线程进入 Time Waiting
    Thread.sleep()
    设置 Timeout 参数的 Object.wait() 方法。
    设置 Timeout 参数的 Object.join() 方法。
    LockSupport.parkNanos() 方法。
    LockSupport.parkUnit() 方法。
  5. 阻塞(Blocked): “阻塞状态” 在等待获取一个 排他锁,需要 另一个线程放弃这个锁的时候发生
  6. 结束(Terminated): 已终止 线程的状态,现在已经 结束执行
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐