前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >并发编程之三大性质

并发编程之三大性质

作者头像
爱撒谎的男孩
发布2019-12-31 15:10:09
5600
发布2019-12-31 15:10:09
举报
文章被收录于专栏:码猿技术专栏

文章目录

1. 多线程的三大性质

1.1. 原子性

1.2. 可见性

1.2.1. 原因

1.2.2. 解决

1.3. 有序性

1.3.1. 指令重排序

1.3.2. 解决

1.4. 参考文档

多线程的三大性质

原子性

  • 对共享变量更新操作的时候,要保证执行不可分割,比如银行转账,一旦在多线程的环境下将其分割了,那么可能造成的后果可能是转账的账户钱少了,但是转到的账户的钱可能不是那么多或者根本没有转过去
  • 在单线程的环境下没有所谓的原子性,都是顺序执行的
  • 多线程的环境下对共享变量的访问(读写)才会涉及原子性
  • 使用Locksychronized可以解决原子性问题

可见性

  • 在多线程的环境下,一个线程的对共享变量的更新,后续的线程访问这个共享变量的时候能否立即读取这个更新后的结果

原因

  1. 程序中的变量可能是被分配到寄存器中,而不是主内存中。每个处理器都有寄存器,而一个处理器的寄存器不能读取另外一个处理器的寄存器的内容。因此如果两个线程运行在不同的处理器上,而共享变量被分配到寄存器上存储,那么可见性问题就出现了
  2. 每一个处理器都有自己的高速缓存区,即使共享变量在主存中, 在线程执行的时候会将复制一个副本存放在高速缓存中,一个线程更新共享变量之后会更新到高速缓存中,还没有来得及更新到主存中的时候,另外的线程就开始执行了,那么此时的共享变量就不是更新之后的,出现了可见性问题
  3. 处理器并不是直接与主内存直接打交道执行读写操作的,而是通过寄存器,高速缓存,写缓冲器和无效化队列等部件执行内存的读写。

解决

  • 虽然每个处理器不能直接读写不同处理器的高速缓存,但是我们可以缓存一致协议来读取其他处理器中的高速缓存的内容,并将读取的内容更新到自己的高速缓存。这种一个处理器从其自身处理器缓存之外的其他存储部件中读取数据并将其更新到该处理器高速缓存的过程称之为缓存同步
  • 使用voliate关键字可以解决可见性问题
    • public voliate int a

有序性

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

指令重排序

代码语言:javascript
复制
int i = 0;              
boolean flag = false;
i = 1;                //语句1  
flag = true;          //语句2

上面代码定义了一个int型变量,定义了一个boolean类型变量,然后分别对两个变量进行赋值操作。从代码顺序上看,语句1是在语句2前面的,那么JVM在真正执行这段代码的时候会保证语句1一定会在语句2前面执行吗?不一定,为什么呢?这里可能会发生指令重排序(Instruction Reorder)。 下面解释一下什么是指令重排序,一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。 比如上面的代码中,语句1和语句2谁先执行对最终的程序结果并没有影响,那么就有可能在执行过程中,语句2先执行而语句1后执行。 但是要注意,虽然处理器会对指令进行重排序,但是它会保证程序最终结果会和代码顺序执行结果相同,那么它靠什么保证的呢?再看下面一个例子:

代码语言:javascript
复制
int a = 10;    //语句1
int r = 2;    //语句2
a = a + 3;    //语句3
r = a*a;     //语句4

这段代码有4个语句,那么可能的一个执行顺序是:

那么可不可能是这个执行顺序呢: 语句2 语句1 语句4 语句3 不可能,因为处理器在进行重排序时是会考虑指令之间的数据依赖性,如果一个指令Instruction 2必须用到Instruction 1的结果,那么处理器会保证Instruction 1会在Instruction 2之前执行。 虽然重排序不会影响单个线程内程序执行的结果,但是多线程呢?下面看一个例子:

代码语言:javascript
复制
//线程1:
context = loadContext();   //语句1
inited = true;             //语句2
 
//线程2:
while(!inited ){
  sleep()
}
doSomethingwithconfig(context);

上面代码中,由于语句1和语句2没有数据依赖性,因此可能会被重排序。假如发生了重排序,在线程1执行过程中先执行语句2,而此是线程2会以为初始化工作已经完成,那么就会跳出while循环,去执行doSomethingwithconfig(context)方法,而此时context并没有被初始化,就会导致程序出错。 从上面可以看出,指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。 也就是说,要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。

解决

  • 使用voliate

参考文档

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-06-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 多线程的三大性质
    • 原子性
      • 可见性
        • 原因
        • 解决
      • 有序性
        • 指令重排序
        • 解决
      • 参考文档
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档