volatile关键字

概述

volatile是java虚拟机提供的轻量级同步机制,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级。

特性

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

JMM java内存模型

  1. 可见性
  2. 原子性
  3. 有序性

可见性

指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
各个线程对主内存中共享变量的操作(读取和赋值)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成,再将变量写回主内存。各线程都有变量的副本拷贝,不同线程之间不能访问对方工作内存空间。线程间通信必须通过主内存完成。
在这里插入图片描述
可见性证明Demo:
如果变量没有volatile,主线程会卡死,如果加上volatile,会正常结束程序

class MyData{
	
    public volatile int num = 0;

    public void addNum(){
        num = 60;
    }
}


public class TestVolatileDemo {

    public static void main(String[] args) {
        //volatile可以保证可见性,及时通知其他线程,主物理内存及时修改。
        seeOKByVolatile();
    }

    public static void seeOKByVolatile() {
        //共享资源
        MyData myData = new MyData();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\tcome in");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            }
            myData.addNum();
            System.out.println("update complete num: " + myData.num);

        }, "AAA").start();

        //另外一个线程是main线程
        while (myData.num == 0){

        }
        System.out.println(Thread.currentThread().getName() + " is over");
    }
}

原子性

即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
例如 a=1是原子性操作,但是a++和a +=1就不是原子性操作

(1)基本类型的读取和赋值操作,且赋值必须是数字赋值给变量,变量之间的相互赋值不是原子性操作。

(2)所有引用reference的赋值操作

(3)java.concurrent.Atomic.* 包中所有类的一切操作

volatile不保证原子性Demo:

class MyData{

    public volatile int num = 0;
    
    //synchronized 可以保证线程操作统一资源类安全,但是大材小用。
    public void addPlus(){
        num ++;
    }
}


public class TestVolatileDemo {

    public static void main(String[] args) {
      
        MyData myData = new MyData();

        //创建20个线程
        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 1; j <= 1000 ; j++) {
                    myData.addPlus();
                }
            }, String.valueOf(i)).start();
        }

        //线程全部计算完之后 开始向下进行。后台默认两个线程,1.main线程 2.GC线程
        while(Thread.activeCount()>2){
            Thread.yield();
        }
        
        System.out.println(myData.num);

    }
}

不满足原子性的原因就是线程自己的工作内存执行了三步如图:

  1. 读取值
  2. 值+1
  3. 赋值
    在这里插入图片描述
    这里借助其他文章例子:
    一个变量i被volatile修饰,两个线程想对这个变量修改,都对其进行自增操作也就是i++,i++的过程可以分为三步,首先获取i的值,其次对i的值进行加1,最后将得到的新值写会到缓存中。
    线程A首先得到了i的初始值100,但是还没来得及修改,就阻塞了,这时线程B开始了,它也得到了i的值,由于i的值未被修改,即使是被volatile修饰,主存的变量还没变化,那么线程B得到的值也是100,之后对其进行加1操作,得到101后,将新值写入到缓存中,再刷入主存中。根据可见性的原则,这个主存的值可以被其他线程可见。
    问题来了,线程A已经读取到了i的值为100,也就是说读取的这个原子操作已经结束了,所以这个可见性来的有点晚,线程A阻塞结束后,继续将100这个值加1,得到101,再将值写到缓存,最后刷入主存,所以即便是volatile具有可见性,也不能保证对它修饰的变量具有原子性。

那么如果解决原子性问题,如果synchronized有点大材小用。所以可以使用atomic包下的AtomicInteger

	//最好的解决办法是用atomic下的的AtomicInteger
    AtomicInteger atomicInteger = new AtomicInteger();
    public void atomicPlus(){
        //类似i++
        atomicInteger.getAndIncrement();

        //类似++i  atomicInteger.incrementAndGet();
        //对应+特殊值 atomicInteger.getAndAdd()
    }

有序性

即程序执行的顺序按照代码的先后顺序执行。

处理器在进行重排序时必须考虑指令之间的数据依赖性

多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致是无法确定的,结果无法预测。

volatile的作用就是禁止指令重排优化
在这里插入图片描述
部分内容参考

https://blog.csdn.net/u012723673/article/details/80682208

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值