Java并发编程(一)---原子性,可见性,有序性

举报
码农飞哥 发表于 2021/05/29 12:58:59 2021/05/29
【摘要】 摘要 并发编程世界里,由于CPU缓存导致的可见性问题,线程切换导致的原子性问题,以及编译器重排序导致的有序性问题是并发编程Bug的根源。 正文 可见性 一个线程对共享变量的修改。另外一个线程能够立刻看到,我们称之为可见性。共享变量指的是存放在堆内存,由所有线程所共享的变量。比如:实例变量,静态变量。 如图所示: 共享变量V可以由线程A和线程B同时操作,线程A和...

摘要

并发编程世界里,由于CPU缓存导致的可见性问题,线程切换导致的原子性问题,以及编译器重排序导致的有序性问题是并发编程Bug的根源。

正文

可见性

一个线程对共享变量的修改。另外一个线程能够立刻看到,我们称之为可见性。共享变量指的是存放在堆内存,由所有线程所共享的变量。比如:实例变量,静态变量。
如图所示:
在这里插入图片描述
共享变量V可以由线程A和线程B同时操作,线程A和B首先从各自的CPU缓存或者寄存器中读取数值,然后由CPU的寄存器写入内存中。

public class LongTest { private static long atest = 0L; public void countTest() { for (int i = 0; i < 10000; i++) { atest = atest + 1; } } public static void main(String[] args) throws InterruptedException { final LongTest longTest = new LongTest(); Thread threadA = new Thread(new Runnable() { public void run() { longTest.countTest(); } }); Thread threadB = new Thread(new Runnable() { public void run() { longTest.countTest(); } }); threadA.start(); threadB.start(); threadA.join(); threadB.join(); System.out.println("*******获得到的atest值为=" + atest); }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

如上程序,运行之后我们会发现 atest 值永远都不会到达20000,而是在10000-20000之间的随机数。
原因分析:假设线程A和线程B同时执行 atest = atest + 1;, 线程A 读取到的原值是0,执行+1操作之后,得到新值1,同样的,线程B也是读取到的原值0,然后执行+1操作,得到新值1。这样就永远得不到结果2。
类推的话,循环执行10000次也是同理,线程A执行+1操作时不能及时获得线程B已经写入的值,故导致值永远不可能达到20000。

原子性

由于一条高级语句在CPU中可能会分成若干条指令来执行,每条指令执行完之后就有可能会发生线程切换。故线程切换造成的原子性问题。

例如: count=count+1 共有三个指令

  • 指令一: 将count值从内存加载到到CPU的寄存器中
  • 指令二:在寄存器中+1操作
  • 指令三 :将新值写入到内存中(由于缓存机制,写入的可能是CPU的缓存而不是内存)

操作系统做任务切换可以发生在任何一条CPU指令执行完,是CPU指令执行完。
我们将一个或者多个操作在CPU执行过程中不被中断的特性称之为原子性。

有序性

编译器重排序导致的有序性问题:
例如:双重加锁中的:

public class SingletonDemo { private static  SingletonDemo instance = null; private SingletonDemo(){ } static SingletonDemo getSingletonDemo() { if (instance == null) { synchronized (SingletonDemo.class) { if (instance == null) { instance = new SingletonDemo(); //6 } } } return instance; }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

这里会有有序性问题:
问题主要出在了 new SingletonDemo() 这一步
因为instance = new SingletonDemo();主要有三个指令

  1. 分配内存空间M
  2. 在内存M上初始化对象
  3. 然后M的地址赋值给instance变量

正常顺序是1-2-3
但是CPU重排序之后执行顺序可能变成了 1-3-2
步骤如下:

  1. A首先进入synchronized,由于instance为null,所以它执行instance = new SingletonDemo();
  2. 然后线程 A执行1->JVM先画出了一些分配给SingletonDemo实例的空白内存,并赋值给instance
  3. 在还没有进行第三步(将instance引用指向内存空间)的时候,恰好发生了线程切换 ,切换到了线程B上,
  4. 如果此时线程B也执行getSingletonDemo()方法,那么线程B 在执行第一个判断是会发现instance!=null,所以直接返回了instance,而此时的instance是没有初始化的。

总结

并发编程中主要的问题就是可见性问题, 原子性问题,有序性问题。本文介绍了这三种问题的发生原因,以及发生的场景。

文章来源: feige.blog.csdn.net,作者:码农飞哥,版权归原作者所有,如需转载,请联系作者。

原文链接:feige.blog.csdn.net/article/details/102782286

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。