线程可见性和原子性,java 可见性 原子性

  线程可见性和原子性,java 可见性 原子性

  00-1010 1.原子性问题2。能见度问题3。订单问题汇总问题:

  1.什么是原子性、可见性和有序性?

  00-1010原子性、可见性和有序性是并发编程面临的三大问题。

  所谓原子操作是“一个或一系列不能中断的操作”,指的是不会被线程调度机制中断的操作。这个操作一旦开始,就会一直运行到最后,中间没有任何线程切换。

  例如,对于I,将实际生成以下JVM字节码指令:

  Getstatic i //获取静态变量I的值(内存值)iconst_1 //准备常数1 addr//自动增量(寄存器增量1)putstatic i //将修改后的值存储在静态变量I中(将值存储在内存中)。如果是单线程或更多线程,8行代码按顺序执行(无交错)。没问题:

  但是,在多线程下,这8行代码可能会交替运行:

  出现负数的情况:

  出现正数的情况:

  自增运算符是一种复合运算。这三个JVM指令,即“从内存中获取值”、“将寄存器递增1”和“将值保存到内存中”,是不可分的。它们是原子的、线程安全的,也称为原子操作。然而,当两个或多个原子操作被组合时,它们就不再是原子的了。比如写前读,有可能读后,其实这个变量被修改了,导致读写数据不一致。

  因为这四个操作可以线程切换,或者被其他线程中断。所以操作不是原子操作,原子性问题会出现在并行场景中。

  00-1010当一个线程修改共享变量时,另一个线程可以立即看到。我们称之为共享变量内存可见。

  说到内存可见性,首先要介绍Java内存模型的概念。JMM规定所有变量都应该存储在公共主存中。当线程使用变量时,它会将主存中的变量复制到自己的工作内存(私有内存)中。线程对变量的读写操作是变量在自己工作内存中的拷贝。

  如果两个线程同时操作一个共享变量,就可能发生可见性问题:

  (1)主存中有一个变量sum,初始值sum=0;

  (2)线程A计划给sum加1,先把sum=0复制到自己的私有内存中,然后更新sum的值。线程A的操作完成后,其私有内存中的和等于1,但线程A将更新后的和刷回主存的时间不固定;

  (3)就在线程A没有把sum刷回主存之前,线程B也从主存读取sum,此时值为0,用线程A进行同样的操作,但没有达到sum=2的预期目标,最终sum=1;

  线程A和线程B并发操作sum发生内存可见性问题:

  为了解决多线程的内存可见性问题,所有线程都必须将共享变量刷新到主存中。一个简单的解决方案是使用Java提供的关键字volatile来修饰共享变量。

  为什么Java局部变量和方法参数没有内存可见性问题?

  在Java中,所有的局部变量和方法定义参数不在线程间共享,所以不会有内存可见性问题。的所有对象实例、类实例和数组元素都存储在JVM堆内存中,堆内存在线程间共享,因此存在可见性问题。

  00-1010程序的有序性是指程序按照代码的顺序执行。如果程序执行的顺序与代码的顺序不同,导致错误的结果,那么就出现了顺序问题。

  @ SLF 4j public class Test3 { private static volatile int x=0,y=0;私有静态int a=0,b=0;公共静态void main(String[] args)引发interrupted exception { for(int I=0;I){ a=0;b=0;x=0;y=0;线程t1=新线程(()-{ a=1;x=b;});线程t2=新线程(()- {

   b = 1; y = a; }); t1.start(); t2.start(); t1.join(); t2.join(); // 假如t1线程先执行,t2线程后执行,则结果为a=1,x=0,b=1,y=1 (0,1) // 假如t2线程先执行,t1线程后执行,则结果为b=1,y=0,a=1,x=1 (1,0) // 假如t1线程和t2线程的指令是同时或交替执行的,则结果为a=1,b=1,x=1,y=1 (1,1) // 但是不可能出现(0,0) if(x==0 && y==0){ log.debug("x:{}, y:{}",x,y); } } }}由于并发执行的无序性,赋值之后x、y的值可能为(1,0)、(0,1)或(1,1)。为什么呢?因为线程t1可能在线程t2开始之前就执行完了,也可能线程t2在线程t1开始之前就执行完了,甚至有可能二者的指令是同时或交替执行的。

  然而,执行以上代码时,出乎意料的事情发生了:这段代码的执行结果也可能是(0,0),部分结果如下:

  

19:37:32.113 [main] DEBUG com.example.test.Test3 - x:0, y:019:37:33.041 [main] DEBUG com.example.test.Test3 - x:0, y:019:37:34.501 [main] DEBUG com.example.test.Test3 - x:0, y:019:37:41.825 [main] DEBUG com.example.test.Test3 - x:0, y:0

 

  

于以上程序来说,(0,0)结果是错误的,意味着已经发生了并发的有序性问题。为什么会出现(0,0)结果呢?可能在程序的执行过程中发生了指令重排序。对于线程t1来说,可能a=1和x=b这两个语句的赋值操作顺序被颠倒了,对于线程t2来说,可能b=1和y=a这两个语句的赋值操作顺序被颠倒了,从而出现了(x,y)值为(0,0)的错误结果。

 

  什么是指令重排序?

  一般来说,CPU为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行顺序同代码中的先后顺序一致,但是它会保证程序最终的执行结果和代码顺序执行的结果是一致的。

  重排序也是单核时代非常优秀的优化手段,有足够多的措施保证其在单核下的正确性。在多核时代,如果工作线程之间不共享数据或仅共享不可变数据,重排序也是性能优化的利器。然而,如果工作线程之间共享了可变数据,由于两种重排序的结果都不是固定的,因此会导致工作线程似乎表现出了随机行为。指令重排序不会影响单个线程的执行,但是会影响多个线程并发执行的正确性。

  事实上,输出了乱序的结果,并不代表一定发生了指令重排序,内存可见性问题也会导致这样的输出。但是,指令重排序也是导致乱序的原因之一。

  总之,要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有得到保证,就有可能会导致程序运行不正确。

  

 

  

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注盛行IT的更多内容!

 

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: