Volatile为什么不保证原子性

Volatile不保证原子性
原子性的相关概念

我们经常提到的就是事务具备原子性,其实原子性简单理解就是某一个线程在进行具体业务的时候,中间不能被分割,要不同时成功,要么同时失败

代码验证
        MyData myData = new MyData();
         //创建20个线程,线程里面进行1000次循环
        for (int i = 1; i <= 20 ; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000 ; j++) {
                    myData.addPlusPlus();
//                    myData.addMyAtommic();
                }
            },String.valueOf(i)).start();
        }

        
        //Thread.activeCount() 来感知线程时候执行结束,为2的原因是。默认线程两个线程,一个main线程,一个是后台垃圾回收线程
        while(Thread.activeCount() > 2){
            //如果活跃线程大于2,表示不执行,等待线程结束
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+"\t finnally number value: "+myData.number);
        System.out.println(Thread.currentThread().getName()+"\t finnally number value: "+myData.atomicInteger);
    }
结果
完成代码如下所示
class MyData{
     int number = 0;
    public void addTo60(){
        this.number = 60;
    }
    public void addPlusPlus(){
        number++;
    }

    AtomicInteger atomicInteger = new AtomicInteger();
    public void addMyAtommic(){
        atomicInteger.getAndIncrement();
    }
}
/*
1 验证volatile的可见性
    1.1 加入int number=0,number变量之前根本没有添加volatile关键字修饰,没有可见性
    1.2 添加了volatile,可以解决可见性问题
2 验证volatile不保证原子性

    2.1 原子性是不可分割,完整性,也即某个线程正在做某个具体业务时,中间不可以被加塞或者分割。
    需要整体完成,要么同时成功,要么同时失败。

    2.2 volatile不可以保证原子性演示

    2.3 如何解决原子性
        *加sync
        *使用我们的JUC下AtomicInteger

* */

class MyData1{
  volatile   int number=0;
    public  void addTo60(){
        this.number=60;
    }
}
public class VolatileDemo {
    public static void main(String[] args){
        /**
         * 例子验证volatile修饰的变量不保证原子性
         */
        MyData myData = new MyData();
        for (int i = 1; i <= 20 ; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000 ; j++) {
                    myData.addPlusPlus();
                    myData.addMyAtommic();
                }
            },String.valueOf(i)).start();
        }

        //需要等待上述20个线程都计算完成后,再用main线程去的最终的结果是多少?
//        try{TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}
        while(Thread.activeCount() > 2){
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+"\t finnally number value: "+myData.number);
        System.out.println(Thread.currentThread().getName()+"\t finnally number value: "+myData.atomicInteger);
        System.out.println();
        System.out.println("-----------------------------------------分割线-----------------------------------------------------------------------------------");
        System.out.println();
        System.out.println();

        /**
         * 例子验证volatile修饰的变量有可见性
         */
        MyData1 myData1=new MyData1();
        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+"\t 进来了");
                TimeUnit.SECONDS.sleep(3);
                myData1.addTo60();
                System.out.println(Thread.currentThread().getName()+"\t updata number value="+myData1.number);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
            }
        },"AA").start();
        //主线程等待
        while (myData1.number==0){

        }
        System.out.println(Thread.currentThread().getName()+"\t 结束");

    }
}
为什么会出现数据丢失

Volatile为什么不保证原子性_第1张图片

如何查看字节码操作
  • 我们使用Idea里面提供的External Tool命令,来扩展javap命令
  • 完事我们在类上点击右键,运行javap -v
  • 分析的的源码嘛和结果如图所示

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yCFav5BO-1603262624880)(https://raw.githubusercontent.com/xiaofeifei321/Picture/master/img20201021142003.png)]

  • 结果说明

    • 我们能够发现n++这条命令,被拆分为4个指令

      • 执行getFiled 从内存拿到原始n
      • iconst_1 将int类型的1推到栈顶
      • iadd 在栈顶将两个int型数值相加并且将结果压入栈顶
      • putFiled 将累加后的值写入主内存

      因为方法上面没加synchronized同步锁,所以在第一步就可能存在,同时多个线程拿到getFiled命令,拿到主内存的值,然后在各自内存中进行变量的相关操作,但是在并发进行iadd操作的时候,只能有一个线程进行写,其他线程就会被挂起,假设线程A进行写操作的时候,在写完之后,由于volatile的可见性,应该告诉其他线程,主线程我更改啦,你们应该去读取主线程里面的最新值,但是因为太快了,其他线程陆序执行了iadd命令,进行写入操作,这就造成其他线程没有接收到主内存n的改变,从而覆盖了原来的值,出现写丢失,让最终结果少于2000

如何解决
  1. 在方法上添加synchronized
  2. 用JUC下面的原子包装类,比如int类型的AtomicInteger来替代

字节码

说明参考

文章为看视频博客学习过程中总结,方便自己以后好复习

https://www.bilibili.com/video/BV18b411M7xz

http://moguit.cn/#/

你可能感兴趣的:(尚硅谷,java基础,java)