5、共享模型之内存

news/2024/4/27 9:52:34

目录

    • 5.1 Java的内存模型
    • 5.2 可见性
      • 1、退不出的循环
      • 2、解决办法:
      • 3、可见性 vs 原子性
    • 5.3 有序性
      • 1、为什么会有指令重排
      • 2、如何禁止指令重排
      • ==3、原理之volatile==
      • 4、happens-before

5.1 Java的内存模型

JMM 即 Java Memory Model(Java内存模型),它定义了主存、工作内存抽象概念

5.2 可见性

1、退不出的循环

现象:
main 线程对 run 变量的修改对于 t 线程不可见,导致了 t 线程无法停止:

@Slf4j(topic = "c.Test32")
public class Test32 {// 易变static boolean run = true;public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{while(true){if(!run) {break;}}});t.start();sleep(1);run = false; // 线程t不会如预想的停下来//System.out.println("run=false");}
}

在这里插入图片描述

分析:
初始状态, t 线程刚开始从主内存读取了 run 的值到工作内存
在这里插入图片描述

因为 t 线程要频繁从主内存中读取 run 的值,JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中,减少对主存中 run 的访问,提高效率
在这里插入图片描述
1 秒之后,main 线程修改了 run 的值,并同步至主存,而 t 是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是旧值
在这里插入图片描述

2、解决办法:

volatile(易变关键字)
它可以用来修饰成员变量和静态成员变量,它可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存

3、可见性 vs 原子性

可见性保证的是在多个线程之间,一个线程对 volatile 变量的修改对另一个线程可见,volatile 不能保证原子性
原子性指的是在某个时刻保证只有一个线程执行临界区的代码

【注意】
synchronized 语句块既可以保证代码块的原子性,也同时保证代码块内变量的可见性。但缺点是synchronized 是属于重量级操作,性能相对更低

【扩展】
如果在前面示例的死循环中加入 System.out.println() 会发现即使不加 volatile 修饰符,线程 t 也能正确看到对 run 变量的修改了

原因: 之后再进行补充

5.3 有序性

JVM 会在不影响正确性的前提下,可以调整语句的执行顺序,这种特性称之为==『指令重排』==,多线程下『指令重排』会影响正确性

1、为什么会有指令重排

从指令集并行来解释为什么需要指令重排:

2、如何禁止指令重排

volatile 修饰的变量,可以禁用指令重排

3、原理之volatile

4、happens-before

happens-before 规定了对共享变量的写操作对其它线程的读操作可见,它是可见性与有序性的一套规则总结

  • 线程解锁 m 之前对变量的写,对于接下来对 m 加锁的其它线程对该变量的读可见
 static int x;static Object m = new Object();public static void main(String[] args) {new Thread(() -> {synchronized (m) {log.info("t1线程给共享变量x赋值为10");x = 10;}}, "t1").start();new Thread(() -> {synchronized (m) {log.info("t2线程读取共享变量x");System.out.println(x);}}, "t2").start();}
  • 线程对 volatile 变量的写,对接下来其它线程对该变量的读可见
volatile static int x;public static void main(String[] args) {new Thread(() -> {x = 10;}, "t1").start();new Thread(() -> {System.out.println(x);}, "t2").start();}
  • 线程 start 前对变量的写,对该线程开始后对该变量的读可见
static int x;
x = 10;
new Thread(()->{System.out.println(x);
},"t2").start();
  • 线程结束前对变量的写,对其它线程得知它结束后的读可见(比如其它线程调用t1.join()等待它结束)
static int x;
Thread t1 = new Thread(()->{x = 10;
},"t1");
t1.start();
t1.join();
System.out.println(x);
  • 线程 t1 打断 t2(interrupt)前对变量的写,对于其他线程得知 t2 被打断后对变量的读可见(通过t2.interrupted 或 t2.isInterrupted)

  • 对变量默认值(0,false,null)的写,对其它线程对该变量的读可见


http://wed.xjx100/news/301235.html

相关文章

控制并发流程,做好线程间的协调

一、概述 1. 什么是控制并发流程? 线程一般是由线程调度器自动控制的,但有些场景需要按照我们程序员的意愿去实现多线程之间相互配合,从而满足业务逻辑。比如: 让线程A等待线程B执行完后再执行等一些相互合作的逻辑&#xff1b…

MP : Human Motion 人体运动的MLP方法

Back to MLP: A Simple Baseline for Human Motion Prediction conda install -c conda-forge easydict 简介 papercodehttps://arxiv.org/abs/2207.01567v2https://github.com/dulucas/siMLPe Back to MLP是一个仅使用MLP的新baseline,效果SOTA。本文解决了人类运动预测的问…

通过构造方法使属性初始化

1 问题 如何使属性初始化。 2 方法 在Student类中定义两个构造方法publicStudent(String name)和public Student(String name,int score)。在使用new运算符创建对象,由于实际参数是一个String类型的数据"林冲",因此在实例化时会调用有一个Stri…

使用CloudOS帮助企业落地云原生PaaS平台

PaaS究竟是什么? IaaS、SaaS的定义很清楚,而PaaS的定义就比较宽泛。所以,很多人把PaaS当做一个万能的“框”,什么都往里装,特别像一排垃圾桶中的那个“其他垃圾”桶,当你拎了一袋垃圾,不知道往…

Vue中如何进行颜色选择与调色板

Vue中如何进行颜色选择与调色板 颜色选择和调色板是Web开发中常用的功能,它们可以帮助用户选择或调整颜色。Vue作为一个流行的JavaScript框架,提供了一些工具和库,可以方便地实现颜色选择和调色板功能。本文将介绍如何在Vue中进行颜色选择和…

5.3.2 因特网的路由协议(二)基于距离向量算法的RIP协议

5.3.2 因特网的路由协议(二)基于距离向量算法的RIP协议 一、RIP协议概念 RIP是Routing Information Protocol缩写,又称为路由信息协议,是最先得到应用的内部网关协议,RIP作为一个常在小型互联网中使用的路由信息协议…

C++11学习笔记(3)——通用工具(上)(包含重要特性智能指针Smart pointer)

1.Pair 在C11中&#xff0c;std::pair是一个模板类&#xff0c;用于将两个值组合成一个单元。它可以将两个不同的类型的值配对在一起&#xff0c;并且提供了对这对值的访问和操作。 std::pair的定义 template<class T1, class T2> struct pair{T1 first;T2 second; };…

基于DDD实现的用户注册流程,很优雅!

欢迎回来&#xff0c;我是飘渺。今天继续更新DDD&微服务的系列文章。 在前面的文章中&#xff0c;我们深入探讨了DDD的核心概念。我理解&#xff0c;对于初次接触这些概念的你来说&#xff0c;可能难以一次性完全记住。但别担心&#xff0c;学习DDD并不仅仅是理论的理解&am…