java中volatile关键词有什么作用?

本文详细解析了Java中volatile关键字的三大特性:可见性、不保证原子性和禁止指令重排,通过具体实例展示了volatile如何在多线程环境中保障数据的一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

volatile关键字的特性

volatile是jvm提供的轻量级的同步机制

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

什么是可见性:

java内存模型Java Memory Model,简称JMM,他是一种抽象的概念,并不真实存在,描述的是一组规则或规范,通过这种规范规定了了程序中各个变量的访问方式

JVM运行程序的实体是线程,JVM为每一个线程分配了一个工作内存,工作内存是每个线程私有的。

JMM规定了所有变量都存储在主内存中(电脑的内存条),所有线程都可以访问主内存,但是线程对变量的读取赋值等操作必须在自己的工作内存中进行,所以首先要将变量拷贝到自己的工作内存,再对变量操作,操作完成后再将变量写回主内存。

每个线程的工作内存中都存储着主内存中变量的副本,线程之间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存。

如有三个线程同时访问主内存拿到了其中age变量的值为10,如果线程一将age的值重新赋值为20,操作完成后将age的值重新写回主内存,此时主内存中age的值改为了20,会通知线程二和线程三重新从主内存中拷贝新的age变量的值,这种一个线程改变了主内存的共享变量,别的线程马上就知道的特性,就叫可见性

测试示例:
VolatileDemo里定义的是普通int类型number,线程ThreadA会去改变VolatileDemo里的number值,主线程要是能检测到number值的变化,则会打破循环,打印"主线程执行完毕"语句

class VolatileDemo {
    int number = 0;

    public void addNum() {
        this.number = 60;
    }
}

public class Test {
    public static void main(String[] args) {
        VolatileDemo volatileDemo = new VolatileDemo();
        //ThreadA线程,开始执行后会等待3秒,3秒后会将number的值改为60
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "进程开始了");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            volatileDemo.addNum();
            System.out.println(Thread.currentThread().getName() + "更新数字number为" + volatileDemo.number);
        }, "ThreadA").start();

        //主线程,如果能够检测到VolatileDemo里的number值不为0了,那么循环就会打破,执行下面的打印语句
        while (volatileDemo.number == 0) {

        }
        System.out.println(Thread.currentThread().getName()+"主线程执行完毕");
    }
}

打印结果:
在这里插入图片描述

试试给number加上volatile关键词

   volatile int number = 0;

打印结果
在这里插入图片描述
可以看到主线程执行了打印语句,说明主线程检测到了number值的变化,为什么加上volatile关键词就能检测到呢,这就是所说的volatile保证了可见性

什么是原子性

原子性指的是某个线程正在做某个业务时,中间不可以被加塞或者被分割,需要整体完成,要么全部成功,要么要不失败
测试volatile不保证原子性

class VolatileDemo {
    volatile int number = 0;

    public void addPlusPlus() {
        number++;
    }
}

public class Test {
    public static void main(String[] args) {
        VolatileDemo volatileDemo = new VolatileDemo();
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int i1 = 0; i1 < 1000; i1++) {
                    volatileDemo.addPlusPlus();
                }
            },String.valueOf(i)).start();
        }
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+"number值为:"+volatileDemo.number);
    }
}

打印结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到这里的值都没有达到20000,而且每次的值都不一样,为什么呢?

这就是因为volatile关键词不保证原子性,所以当20个线程同时去修改主内存的值的时候,比如一个线程执行了一次addPlusPlus方法,number的值加了1,会马上把值写会到主内存中,然后别的线程就会拿到这个新的值开始自己的计算

但是如果A和B线程同时修改了值,A线程修改了之后,写回主内存,B线程只有重新拿到主线程的值,再进行计算,但是A和B是同时修改了,B修改了之后没有写回到主内存,反倒是重新从主内存中拿了一次数据回来,所以B线程刚刚的操作就丢失了,导致少了一次运算,这也就是为什么总的数值达不到2000,是因为中途丢失了太多操作了,当然也有可能能够达到20000,可能性比较低

如何解决不保证原子性?

  1. 使用synchronized关键词
    public synchronized void addPlusPlus() {
        number++;
    }

打印结果
在这里插入图片描述
可以达到我们的要求,但是synchronized是重量级锁,有没有轻量级的解决方案?
2. 使用AtomicInteger

class VolatileDemo {
    AtomicInteger atomicInteger = new AtomicInteger();

    public synchronized void addPlusPlus() {
        atomicInteger.getAndIncrement();
    }
}

public class Test {
    public static void main(String[] args) {
        VolatileDemo volatileDemo = new VolatileDemo();
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int i1 = 0; i1 < 1000; i1++) {
                    volatileDemo.addPlusPlus();
                }
            },String.valueOf(i)).start();
        }
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+"number值为:"+volatileDemo.atomicInteger);
    }
}

打印结果
在这里插入图片描述

什么是禁止指令重排

计算机在执行程序是,为了提高性能,编译器和处理器常常会对指令做重排,一般分一下3种:

  1. 编译器优化的重排
  2. 指令并行的重排
  3. 内存系统的重排

源代码–>重排–>最终执行的指令

单线程环境里确保程序最终执行结果和代码顺序执行的结果一致
多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,记过无法预测
处理器在进行重排序时必须要考虑指令之间的数据依赖性

内存屏障又称为内寸栅栏,是一个CPU指令,作用有两个:

  1. 保证特性操作的执行顺序
  2. 保证某些变量的内存可见性

由于编译器和处理器都能执行指令重排优化,如果在指令间插入一条内存屏障,则会告诉编译器和CPU不管什么指令都不能对这条内存屏障指令重排序
volatile禁止指令重排优化,从而避免了多线程环境中程序出现乱序执行的现象,

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值