Volatile为什么不保证原子性

it2024-07-22  41

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 结束"); } }
为什么会出现数据丢失

如何查看字节码操作
我们使用Idea里面提供的External Tool命令,来扩展javap命令 完事我们在类上点击右键,运行javap -v

分析的的源码嘛和结果如图所示

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

结果说明

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

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

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

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

字节码

说明参考

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

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

http://moguit.cn/#/

最新回复(0)