300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > JAVA多线程基础篇 4 可见性 有序性与Volatile

JAVA多线程基础篇 4 可见性 有序性与Volatile

时间:2019-03-19 20:55:08

相关推荐

JAVA多线程基础篇 4 可见性 有序性与Volatile

文章目录

1. 可见性问题和有序性问题# 2. 可见性问题的实验2.1 volatile确保了可见性3. 一个指令乱序的实验总结

1. 可见性问题和有序性问题

在多线程开发中,可见性问题和有序性问题需要依托volatile来保证。

可见性,一个线程对共享变量的修改,另外一个线程能够立刻看到。

首先需要了解一个概念,堆和栈。

程序在运行的过程中,数据放在堆中,线程内部的缓存数据会放在栈中。

在堆中的数据是公共的,而在线程的栈帧里的数据是缓存各自线程的。

因此,当A、B两个线程,无论谁更新了线程内的数据,都及时同步并告知其他线程重新刷新读取缓存。java使用volatile 修饰的变量,通过操作系统底层的锁总线(LOCK#)或者缓存一致性协议(MESI)实现可见性。

Lock 会对总线进行锁定,其它 CPU 对内存的读写请求都会被阻塞,直到锁释放。LOCK的开销比较大,锁总线期间其他 CPU 没法访问内存。于是引入了缓存一致性协议(MESI)来实现。MESI 当一个缓存代表它所属的处理器去读写内存时,其它处理器都会得到通知,它们以此来使自己的缓存保持同步。 只要某个处理器写内存,其它处理器马上知道这块内存在它们的缓存段中已经失效。

有序性,即程序执行的顺序按照代码的先后顺序执行。

在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。

编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。内存系统的重排序。由于处理器使用缓存和读 / 写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

通过synchronized和Lock来保证有序性,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。

通过使用volatile来保证有序性,对一个 volatile 域的写,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

# 2. 可见性问题的实验

public class VolatileTest {private static boolean flag = true;public static void main(String[] args) throws InterruptedException {new Thread(()->{while (flag) {}System.out.println("over");}).start();Thread.sleep(5);flag=false;}}

此时,程序是不会有任何输出的,因为在更改flag为false后,线程栈帧内存储的变量没有被改变。

2.1 volatile确保了可见性

如下程序,简单增加volatile修饰后可以达到效果。

public class VolatileTest {private volatile static boolean flag = true;public static void main(String[] args) throws InterruptedException {new Thread(()->{while (flag) {}System.out.println("over");}).start();Thread.sleep(5);flag=false;}}

运行结果:

注意:volatile一般修饰基础类型,尽量不要去修饰引用类型。

3. 一个指令乱序的实验

如果没有乱序执行,则只可能x和y 分别同时为1 ,或者是1 0 组合 或者是 0 1 组合。

一定不能出现x和y同时为0.

public class VolatileTest2 {static int a;static int b;static int y;static int x;public static void main(String[] args) throws InterruptedException {int count = 0;while (true) {a = 0;b = 0;y = 0;x = 0;Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {a = 1;x = b;}});Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {b = 1;y = a;}});t1.start();t2.start();t1.join();t2.join();count++;if (x == 0 && y == 0) {System.out.println("第"+count+"次");break;}}}}

运行结果显示,出现了x和y同时为0。说明程序命令a = 1;x = b;b = 1;y = a发生了乱序执行。

第183383次 (0,0)Process finished with exit code 0

总结

java使用volatile 修饰的变量,通过操作系统底层的锁总线(LOCK#)或者缓存一致性协议(MESI)实现可见性。

java对一个 volatile 域的写,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

多线程系列在github上有一个开源项目,主要是本系列博客的实验代码。

/forestnlp/concurrentlab

如果您对软件开发、机器学习、深度学习有兴趣请关注本博客,将持续推出Java、软件架构、深度学习相关专栏。

您的支持是对我最大的鼓励。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。