您的位置:首页 > 汽车 > 时评 > 中国兼职设计师网_总结网站推广策划书的共同特点_怎样把产品放到网上销售_广州推广seo

中国兼职设计师网_总结网站推广策划书的共同特点_怎样把产品放到网上销售_广州推广seo

2025/5/9 20:08:05 来源:https://blog.csdn.net/m0_73804764/article/details/145689249  浏览:    关键词:中国兼职设计师网_总结网站推广策划书的共同特点_怎样把产品放到网上销售_广州推广seo
中国兼职设计师网_总结网站推广策划书的共同特点_怎样把产品放到网上销售_广州推广seo

一、为什么需要内存模型(JMM)?

在多核CPU时代,并发编程是提升程序性能的核心手段,但是并发带来了三大经典问题:

  1. 可见性问题:线程A修改的变量,线程B无法立即看到

  2. 原子性问题:i++操作在机器指令层面可能被中断

  3. 有序性问题:代码执行顺序与编写顺序不一致

这些问题的根源在于:

  • CPU多级缓存架构导致内存可见性问题

  • 编译器/处理器优化引发指令重排序

  • 线程切换带来的原子性破坏

CPU Cache 示意图

二、JMM架构设计

2.1 内存抽象模型

JMM定义了两个核心概念:

  • 主内存(Main Memory):所有线程共享的内存区域

  • 工作内存(Working Memory):每个线程私有的内存副本

从上图来看,线程 1 与线程 2 之间如果要进行通信的话,必须要经历下面 2 个步骤:

  1. 线程 1 把本地内存中修改过的共享变量副本的值同步到主内存中去。
  2. 线程 2 到主存中读取对应的共享变量的值。

也就是说,JMM 为共享变量提供了可见性的保障。

不过,多线程下,对主内存中的一个共享变量进行操作有可能诱发线程安全问题。举个例子:

  1. 线程 1 和线程 2 分别对同一个共享变量进行操作,一个执行修改,一个执行读取。
  2. 线程 2 读取到的是线程 1 修改之前的值还是修改后的值并不确定,都有可能,因为线程 1 和线程 2 都是先将共享变量从主内存拷贝到对应线程的工作内存中。

2.2 内存交互协议

JMM定义了8种原子操作保证内存可见性

操作作用范围说明
lock主内存变量标记变量为线程独占状态
unlock主内存变量释放变量的锁定状态
read主内存变量将变量值传输到线程工作区
load工作内存将read的值放入变量副本
use工作内存将变量值传递给执行引擎
assign工作内存将执行结果赋值给变量副本
store工作内存将变量值传送到主内存
write主内存变量将store的值放入主内存变量

三、happens-before原则

3.1 核心规则

  1. 程序顺序规则:单线程内的操作按代码顺序保证有序性

  2. volatile规则:volatile写操作先于后续的读操作

  3. 锁规则:解锁操作先于后续的加锁操作

  4. 传递性规则:A→B且B→C,则A→C

  5. 线程启动规则:Thread.start()先于线程内所有操作

下面这张是 《Java 并发编程的艺术》这本书中的一张 JMM 设计思想的示意图:

3.2 实际案例

// 示例:双重检查锁定单例模式
public class Singleton {private volatile static Singleton instance;public static Singleton getInstance() {if (instance == null) {                 // 第一次检查synchronized (Singleton.class) {    // 加锁if (instance == null) {         // 第二次检查instance = new Singleton(); // volatile写}}}return instance;}
}

这里volatile的happens-before关系保证了:

  1. 对象初始化完成 → 写操作

  2. 写操作 → 读操作

四、内存屏障与指令重排序

4.1 屏障类型

屏障类型作用
LoadLoad屏障禁止操作重排序
StoreStore屏障禁止操作重排序
LoadStore屏障禁止读后写重排序
StoreLoad屏障禁止写后读重排序(全能屏障)

4.2 volatile实现原理

public class VolatileExample {private volatile int flag = 0;public void writer() {flag = 1;  // StoreStore屏障 + StoreLoad屏障}public void reader() {if (flag == 1) {  // LoadLoad屏障 + LoadStore屏障// do something}}
}

volatile变量的读写会插入内存屏障:

  • 写操作前插入StoreStore屏障

  • 写操作后插入StoreLoad屏障

  • 读操作前插入LoadLoad屏障

  • 读操作后插入LoadStore屏障

五、JMM与JVM内存结构对比

特性JMMJVM内存结构
关注点多线程内存可见性问题内存区域划分与管理
核心概念主内存、工作内存堆、栈、方法区等
规范级别语言级内存模型虚拟机实现规范
可见性保证通过happens-before规则不直接处理可见性问题
典型应用volatile、synchronized语义对象分配、垃圾回收

六、并发问题解决方案

6.1 可见性问题

可见性问题概述在多线程环境中,每个线程都有自己的工作内存,线程对变量的操作是先从主内存拷贝到工作内存,操作完成后再写回主内存。这就可能导致一个线程对变量的修改,其他线程不能及时看到,从而引发可见性问题。

  • volatile关键字:强制所有读写直接操作主内存


public class VolatileVisibilityExample {private volatile boolean flag = false;public void writer() {flag = true; // 写操作直接更新主内存}public void reader() {while (!flag) {// 等待 flag 变为 true}// 由于 flag 是 volatile 变量,能及时看到 writer 线程对 flag 的修改System.out.println("Flag is now true");}
}

  • synchronized同步块:解锁时自动刷新工作内存到主内存

public class SynchronizedVisibilityExample {private boolean flag = false;private final Object lock = new Object();public void writer() {synchronized (lock) {flag = true; // 修改共享变量} // 退出同步块,将修改刷新到主内存}public void reader() {synchronized (lock) {// 进入同步块,从主内存读取最新的 flag 值if (flag) {System.out.println("Flag is true");}}}
}

  • final关键字:正确发布的不可变对象保证可见性

public class FinalVisibilityExample {private final int value;public FinalVisibilityExample(int value) {this.value = value; // 初始化 final 变量}public int getValue() {return value;}
}

6.2 原子性问题

原子性问题概述:原子性是指一个操作或一系列操作要么全部执行要么全部不执行,不会被其他线程中断。在多线程环境中,如果多个线程同时对一个共享变量进行读写操作,可能会导致数据不一致的问题,因为这些操作可能不是原子性的。

  • Atomic原子类:基于CAS实现无锁编程

import java.util.concurrent.atomic.AtomicInteger;public class AtomicExample {private AtomicInteger counter = new AtomicInteger(0);public void increment() {counter.incrementAndGet(); // 原子性自增操作}public int getCounter() {return counter.get();}
}

  • synchronized同步:通过互斥保证原子性

public class SynchronizedAtomicExample {private int counter = 0;public synchronized void increment() {counter++; // 同步方法,保证原子性}public synchronized int getCounter() {return counter;}
}

  • Lock接口实现:显式锁控制临界区

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class LockAtomicExample {private int counter = 0;private final Lock lock = new ReentrantLock();public void increment() {lock.lock();try {counter++; // 临界区,保证原子性} finally {lock.unlock();}}public int getCounter() {lock.lock();try {return counter;} finally {lock.unlock();}}
}

6.3 有序性问题

有序性问题概述:在多线程环境中,编译器和处理器为了提高性能,可能会对指令进行重排序。重排序可能会导致程序的执行顺序与代码的编写顺序不一致,从而引发有序性问题。

  • volatile:禁止指令重排序

public class VolatileOrderingExample {private int a = 0;private volatile boolean flag = false;public void writer() {a = 1;         // 操作 1flag = true;   // 操作 2,由于 flag 是 volatile 变量,操作 1 不会重排序到操作 2 之后}public void reader() {if (flag) {    // 操作 3int i = a; // 操作 4,操作 4 不会重排序到操作 3 之前}}
}

  • synchronized:保证临界区内代码串行执行

public class SynchronizedOrderingExample {private int a = 0;private int b = 0;private final Object lock = new Object();public void writer() {synchronized (lock) {a = 1; // 操作 1b = 2; // 操作 2,操作 1 和操作 2 会按顺序执行}}public void reader() {synchronized (lock) {int x = b; // 操作 3int y = a; // 操作 4,操作 3 和操作 4 会按顺序执行}}
}

  • final:正确构造的对象保证初始化安全

public class FinalOrderingExample {private final int value;public FinalOrderingExample(int value) {this.value = value; // 正确初始化 final 字段}public int getValue() {return value;}
}

七、实战:诊断内存可见性问题

public class VisibilityDemo {boolean ready = false;int result = 0;int number = 1;public void write() {number = 2;          // 操作1ready = true;        // 操作2}public void read() {if (ready) {         // 操作3result = number; // 操作4}}
}

可能出现的结果:

  • 线程A执行write()

  • 线程B执行read()可能得到result=1(指令重排序导致操作2先于操作1)

解决方案:

  1. 将ready声明为volatile

  2. 使用synchronized同步方法

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com