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);
}
结果
![image-20201021104940508](https://raw.githubusercontent.com/xiaofeifei321/Picture/master/img20201021135048.png)
完成代码如下所示
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命令
![查看++字节码](https://raw.githubusercontent.com/xiaofeifei321/Picture/master/img20201021135443.png)
- 完事我们在类上点击右键,运行javap -v
![查看++字节码1](https://raw.githubusercontent.com/xiaofeifei321/Picture/master/img20201021135556.png)
-
分析的的源码嘛和结果如图所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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
-
如何解决
- 在方法上添加synchronized
- 用JUC下面的原子包装类,比如int类型的AtomicInteger来替代
字节码
说明参考
文章为看视频博客学习过程中总结,方便自己以后好复习
https://www.bilibili.com/video/BV18b411M7xz
http://moguit.cn/#/