Plantre Plantre
首页
后端
技术
硬件
  • 前端文章

    • HTML
    • CSS
    • JavaScript
  • 技术

    • 技术文档
    • GitHub技巧
    • Nodejs
    • 博客搭建
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

plantre

一个后端开发者
首页
后端
技术
硬件
  • 前端文章

    • HTML
    • CSS
    • JavaScript
  • 技术

    • 技术文档
    • GitHub技巧
    • Nodejs
    • 博客搭建
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 计算机组成原理

  • 操作系统

  • 计算机网络

  • 设计模式

  • Java

    • Java基础

    • Java集合

    • Java并发

      • 共享变量副本与 主内存共享变量的区别
        • 创建线程池的几种方式
        • 线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,为什么
        • 异步编程工具CompletableFuture
      • JVM

    • Spring

    • SpringCloud

    • MySQL

    • Redis

    • 分布式

    • Zookeeper

    • Dubbo

    • Kafka

    • 数据结构

    • 算法

    • OJ

    • 从道家哲学看计算机?
    • 后端
    • Java
    • Java并发
    plantre
    2025-06-12
    目录

    共享变量副本与 主内存共享变量的区别

    # 一、核心区别

    维度 主内存共享变量 共享变量副本(工作内存)
    存储位置 JVM 堆内存中的全局共享区域(如实例变量、静态变量) 线程私有的本地内存(CPU 缓存、寄存器等硬件优化空间)
    数据可见性 所有线程可见,但需同步机制保证最新状态 线程私有,默认对其他线程不可见
    同步机制 通过 volatile、synchronized 或原子类强制刷新副本到主内存 默认无同步,线程直接操作副本,可能导致数据不一致

    # 二、Java 代码示例与说明

    # 1. 可见性问题:线程副本未同步导致死循环

    public class VisibilityDemo {
        private static boolean flag = false; // 主内存共享变量
    
        public static void main(String[] args) throws InterruptedException {
            new Thread(() -> {
                System.out.println("Thread 1: Waiting...");
                while (!flag) { // 操作工作内存中的副本
                    // 空循环
                }
                System.out.println("Thread 1: Detected flag change!");
            }).start();
    
            Thread.sleep(1000);
            
            new Thread(() -> {
                System.out.println("Thread 2: Changing flag...");
                flag = true; // 修改副本,但未及时刷新到主内存
            }).start();
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    输出结果:

    Thread 1: Waiting...
    Thread 2: Changing flag...
    (线程1永远无法退出循环)
    
    1
    2
    3

    原因:线程2修改 flag 后未强制同步到主内存,线程1的工作内存副本仍为旧值 false。


    # 2. 使用 volatile 解决可见性问题

    private static volatile boolean flag = false; // 强制读写直接操作主内存
    
    1

    输出结果:

    Thread 1: Waiting...
    Thread 2: Changing flag...
    Thread 1: Detected flag change!
    
    1
    2
    3

    机制:volatile 通过内存屏障禁止指令重排序,并强制线程每次读写直接从主内存操作。


    # 3. 使用 synchronized 保证可见性

    public class SynchronizedDemo {
        private static boolean flag = false;
    
        public static void main(String[] args) throws InterruptedException {
            new Thread(() -> {
                synchronized (SynchronizedDemo.class) {
                    System.out.println("Thread 1: Waiting...");
                    while (!flag) {
                        // 空循环
                    }
                    System.out.println("Thread 1: Detected flag change!");
                }
            }).start();
    
            Thread.sleep(1000);
    
            new Thread(() -> {
                synchronized (SynchronizedDemo.class) {
                    System.out.println("Thread 2: Changing flag...");
                    flag = true;
                }
            }).start();
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    输出结果:线程1正常退出循环。
    机制:synchronized 在加锁时清空工作内存并强制从主内存读取最新值,解锁前将修改刷新到主内存。


    # 4. 复合操作的原子性问题

    public class AtomicityDemo {
        private static volatile int count = 0;
    
        public static void main(String[] args) throws InterruptedException {
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 1000; j++) {
                        count++; // 非原子操作(读取-修改-写入)
                    }
                }).start();
            }
    
            Thread.sleep(2000);
            System.out.println("Final count: " + count); // 结果可能小于 10000
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    原因:count++ 是三个操作的组合(读取主内存值 → 修改副本 → 写回主内存),即使使用 volatile 也无法保证原子性。

    解决方案:
    • 使用 AtomicInteger(基于 CAS 原子指令):

    private static AtomicInteger count = new AtomicInteger(0);
    count.incrementAndGet(); // 原子操作
    
    1
    2

    • 使用 synchronized 块:

    synchronized (AtomicityDemo.class) {
        count++;
    }
    
    1
    2
    3

    # 三、总结

    1. 主内存与副本的本质差异:
      • 主内存是全局唯一真实值,副本是线程私有临时缓存。
      • 线程默认操作副本,需同步机制保证主内存与副本的一致性。

    2. 同步工具选择:
      • volatile:轻量级解决可见性和有序性,不保证原子性(如 count++)。
      • synchronized:保证可见性、原子性和有序性,但可能引发线程阻塞。
      • 原子类(如 AtomicInteger):无锁实现原子操作,适合高并发场景。

    通过合理使用同步机制,可避免因副本与主内存不一致导致的多线程问题。

    编辑 (opens new window)
    上次更新: 2025/06/13, 00:51:28
    java里flush()什么作用
    创建线程池的几种方式

    ← java里flush()什么作用 创建线程池的几种方式→

    最近更新
    01
    集成loki
    07-04
    02
    TCP的ESTABLISHED是什么意思
    06-24
    03
    安装1panel
    06-24
    更多文章>
    Theme by Vdoing | Copyright © 2025-2025 plantre | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式