volatile | 8lovelife's life
0%

volatile

线程间的通信

线程间的通信方式有:共享内存和消息传递

共享内存

多个线程通过读写内存中的共享对象来隐式的进行通信。如Java中的对象

消息传递

线程间通过发送消息显示的进行通信。如Java中的wait()/notify()

线程间共享内存

线程间共享的对象都在主存中,每个线程都会有一块私有的本地内存称为栈,线程栈中存储了共享对象的副本

线程安全

线程安全包括几个方面的内容:1。原子性 2。可见性 3。有序性

原子性(执行控制-代码顺序)

原子性提供了多线程间代码指令的互斥访问。如:Synchronized、Lock

可见性

一个线程对共享内存数据的修改是否会立刻被其他线程感知到?多线程间的共享数据访问会由于缓存的不一致性导致数据错乱
image

有序性

编译器或处理器为了优化指令的执行效率,无数据依赖关系的指令执行顺序会被重新排序(在单线程环境下,指令重排序执行的最终结果应该与其在顺序执行下的效果一致,即 as-if-serial semantics)。然而多线程下会因为不同的执行顺序 ,导致不同的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
String content;
boolean isReadable;

void write(){
content = "Some Contents";
isReadable = true;
}

void doWork(){
while(isReadable){
System.out.println(content)
}
}

假设线程A运行的write()方法中代码顺序进行了重排序,则线程B运行的doWrok()中并不一定能够读到更改后的content!

数据依赖

存在数据依赖的语句不会被重排序,如下图的数据依赖关系

数据依赖 示例
写后读 x = 10 ; y=x
写后写 x=10 ; x=20
读后写 y=x ; x=5

JMM中确保的有序性

如果两个操作的执行顺序无法从happens-before原则推导出来,那么就不能保证操作的有序性,JVM可以对它们进行随意的重排序。happens-before原则如下:

  • 程序次序规则:单线程内,按照代码顺序执行,书写在前的代码先发生于书写在后的代码
  • 锁定规则:一个unLock操作先发生于后面的lock操作之前
  • volatile原则:一个volatile变量的写操作先发生于后面对这个变量的读操作
  • 传递原则:A操作先发生于B操作,B操作先发生于C操作 ,则A操作先发生于C操作
  • Thread启动原则:Thread的start()操作先发生于此线程的其他操作
  • Thread中断原则:Thread的interrupt()操作先发生于代码对线程中断事件的检测
  • Thread终止原则:Thread的所以操作都先发生于线程的终止检测
  • 对象终结原则:对象的初始化完成先发生于对象的finalize()方法的开始

volatile

如果字段被声明为volatile,JMM确保所有线程看到这个变量的值是一致的。JMM通过volatile声明来隐藏处理针对不同处理器下的数据缓存一致性问题,使用Lock前缀或者内存屏障。计算机中的缓存一致性方案有两种:总线锁机制和MESI协议(缓存一致性协议)

volatile的作用

  • 禁止指令重排序
  • 强制将缓存的修改立即写入主存
  • 如果是写操作,将导致其他CPU对应的缓存行无效

    volatile不能保证执行的原子性,无法保证原子性的操作都不能保证线程的安全

禁止指令重排序

  1. 在执行volatile读写操作语句时,在此之前的语句全部已经进行,且结果已经对后面的操作可见
  2. 在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行

Why do we accept the love we think we deserve? - Truman Burbank

The Truman Show