Volatile关键字

描述

  • 描述

    • volatile是jvm提供的一种轻量级的同步机制(Synchronized是重量级),
  • 作用

    • 用来确保将变量的更新操作通知到其他线程
  • 为什么是轻量级

    • 在访问volatile变量时不会执行加锁操作,因此也不会执行线程阻塞操作。
  • volatile的读性能消耗与普通变量相同,但写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不会发生乱序执行。

特性

  • 保证了可见性
  • 不保证原子性
  • 禁止指令重排

可见性

不用volatile关键字:

代码:

public class JMMDemo {
    private static int num = 0;
 
    public static void main(String[] args) {
        new Thread(()->{
            while (num ==0){
 
            }
        }).start();
 
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 
        num = 1;
        System.out.println(num);
    }
}

运行结果:并没有结束,而是不断运行循环

在这里插入图片描述

解析:

  • 开辟一个子线程,只要子线程检测到静态变量为0,就让它一直执行下去。

  • 在让num值变为1之前,主线程睡了1s。主线程醒了之后,num的值变成1.但是子线程却却不会停止运行,那么这是什么原因呢?

  • 主线程和子线程之间是独立运行的。假如让主线程睡眠1s,子线程无法感知到主线程做了什么。它们之间的连接在这1s中断开了。当主线程睡醒后,和子线程分离了。所以此时子线程还是拿着主线程sleep之前的数据进行操作,而没有进行最新的操作。

怎么解决?

  • 当主线程的值出现了覆盖现象时,子线程却无法获取到主线程的最新数据,多线程就会乱套了。
  • 所以当多线程操作时,一定要加上同步操作,以确保子线程可以实时更新到新数据。这时候就使用了volatile关键字!

使用volatile关键字:

代码:

在这里插入图片描述

运行结果:感知到主线程更新数据,跳出子线程循环
在这里插入图片描述

不保证原子性

代码:如果保证了原子性,线程数结果应该是20000

public class Atomic {
    private volatile static int num = 0;
 
    public static void add(){
        num++;
    }
 
    public static void main(String[] args) {
        //理论上num结果应该是20x1000=2万
        for (int i = 1; i <=20 ; i++) {
            new Thread(()->{
                for (int j=0; j < 1000 ; j++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount()>2) {    //如果存活的线程大于2个,即除了main和gc还有其他线程
            Thread.yield();     //执行线程礼让
        }
        System.out.println(Thread.currentThread().getName()+" " +num);
    }
}

运行结果:

  • main执行的线程每次都不到20000.说明出现了覆盖写的现象,也就是说没有保证原子性;

在这里插入图片描述

解析:
num++这个操作分成了3步执行

  • 获得这个值
  • +1
  • 写回这个值

假设线程1执行num++,执行到了第二步+1时还没写入主存时,时间片就用尽了。当其他线程更新完主存后,线程1由于已经执行完获取值的操作而不会再次读取新num值,导致线程1的num值覆盖掉了线程2的num值。故,破坏了原子性。那么怎么来解决这个问题呢?

解决:

  • JUC提供了一个atomic包,使用其中的AtomicInteger类,调用num的getAndIncrement方法执行+1操作。这个方法基于CAS原理,它其实是调用操作系统的方法直接修改内存中的值。
  • 再次运行时,就保证了原子性,运行结果为200000.
  • 在这里插入图片描述

CAS原理

禁止指令重排

描述:

  • 计算机并不是按照你写程序的顺序去执行的。
  • 指令重排时不同变量如果有依赖关系是不会进行重排的。也就是说,处理器在进行指令重排时会考虑:数据之间的依赖性

volatile是怎么禁止指令重排呢?

  • volatile关键字会让这个变量前后都加一层内存屏障,内存屏障的意思是禁止上面指令和下面指令顺序进行交换

场景

哪些地方用到了volatile呢?

  • 单例模式
  • 读写锁
  • JUC中的大量方法
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值