13.JMM和Volatile

JMM

对Volattile的理解

Volatile是java虚拟机提供的轻量级的同步机制

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排

可见性怎么保证,JMM

JMM java内存模型,不存在的东西,概念

关于JMm的一些同步的约定

  1. 线程解锁前,必须把共享变量立刻刷回到主存
  2. 线程加锁前,必须读取主存中的最新值到工作内存
  3. 加锁和解锁是同一把锁

8种操作

  • lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
  • unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
  • read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
  • use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
  • assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
  • store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
  • write  (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中

对八种操作的规则

  1. 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
  2. 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
  3. 不允许一个线程将没有assign的数据从工作内存同步回主内存
  4. 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
  5. 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
  6. 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
  7. 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
  8. 对一个变量进行unlock操作之前,必须把此变量同步回主内存

问题:程序不知道主内存的值已经修改过了?怎么办?Volatile

//num已经被main线程修改为1,刷回了主内存,但new thread读不到,num还是0,所以程序就不会停止
public class VolatileTest {
    private static int num = 0;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (num == 0) {
                // System.out.println(num);
            }
        }).start();
        TimeUnit.SECONDS.sleep(1);
        num=1;
        System.out.println(num);
    }
}

Volatile

保证可见性,不保证原子性,避免指令重排

保证可见性

public class VolatileTest {
    private volatile static int num = 0;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (num == 0) {
                
            }
        }).start();
        TimeUnit.SECONDS.sleep(1);
        num=1;
        System.out.println(num);
    }
}

不保证原子性

原子性:不可分割

线程A在执行任务的时候,不能被打扰,不能被分割,要么同时成功,要么同时失败

测试volatile不保证原子性

/*
* volatile 不保证原子性
* */
public class VolatileAtomicTest {
    private static int a = 0;

    public static void main(String[] args) throws InterruptedException {
        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 1; j <= 1000; j++) {
                    //add1();
                    add();
                }
            }).start();
        }
        while (Thread.activeCount() > 2) {//main和gc,大于2表示还有new Thread在运行。这个能跑完说明线程一定跑完了
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() + "==>" + a);//!=2000
    }
    public static void add() {
        a++;//不是原子操作
    }
}

分析class字节码文件

add方法有如下操作

1、获取i
2、i自增
3、回写

D:\tools\juc\target\classes\volatiletest>javap -c VolatileAtomicTest.class

怎么保证原子性

  1. 如果不加lock和synchronized,怎么保证原子性?

  2. 原子操作类:java.util.concurrent.atomic

    原子类的底层都是和操作系统相关,在内存中修改值!Unsafe类一个很特殊的存在

/*
* 使用原子操作类保证原子性java.util.concurrent.atomic
* */
public class VolatileAtomicTest1 {
    private static volatile AtomicInteger a = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 1; j <= 1000; j++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount() > 2) {//main和gc,大于2表示还有new Thread在运行。这个能跑完说明线程一定跑完了
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() + "==>" + a);//Integer保证了原子性
    }

    public static void add() {
        //getAndIncrement相当于a++
        //incrementAndGet相当于++a
        a.getAndIncrement();//CAS
    }
}

指令重排

什么是指令重排:你写的程序,计算机并不是按你写的那样执行的

源代码--->编译器优化的重排--->指令并行也可能会重排--->内存系统也会重排--->执行

处理器在进行指令重排的时候,会考虑:数据之间的依赖性!

int x=1; //1
int y=2; //2
x=x+5;   //3
y=x*x;   //4

我们期望的:1234 但是执行时可能是: 2134 1324,这些都是正确的
    4123是不可能的

可能造成影响的结果:ab xy初始值都是0

线程A 线程B
x = a y = b
b = 1 a = 2

正常结果:x = 0,y = 0,但是由于可能指令重排,线程A和B重排后顺序可能如下


线程A 线程B
b = 1 a = 2
x = a y = b

指令重排导致的非正常结果:x = 2,y = 1

volatile可以避免指令重排

内存屏障,就是CPU指令。作用:

  1. 保证特定的操作的执行顺序

  2. 可以保证某些变量的内存可见性

Volatile是可以保证可见性,不能保证原子性,由于内存屏障,可以避免指令重排

posted @   jpy  阅读(20)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示