java中线程之间的共享变量存储在主内存(java堆)中,每个线程都有一个私有的本地内存,本地内存存储了该线程以读、写共享变量的副本。本地内存是一个抽象概念,并不真实存储。它涵盖了cache,寄存器记等等。
线程之间的通信
线程之间的通信采用的是共享内存的方式,整个通信的过程由JMM(java内存模型)来控制的。线程A与线程B之间要通信必须要经历下面两个过程:
- 线程A把本地内存A中更新过的共享变量刷新到主内存中去
- 线程B到主内存中读取线程A之前已更新过的共享变量
重排序
java编译器/解释器为了优化程序代码重排序。我们先来看一个例子:
public class Test {
static boolean flag = true;
static float money = 0f;
public static void main(String[] args) {
flag = false;
money = 0.5f;
new Thread() {
@Override
public void run() {
test();
}
}.start();
}
static void test() {
while (!flag)
Thread.yield();
System.out.println("money == " + money);
}
}
上面的代码要么输出"money==0.5"(几率很小很小),要么什么都不输出。这是因为编译器可能将flag = false和money = 0.5f进行的重排序。即money = 0.5f出现在了flag=false的前面。这样的话,当mony=0.5f时发生了线程切换,test方法得到了执行。而这是flag值是为true的,所以输出了"money == 0.5"。那么这样的话,我们写的代码得不到控,是不是很无奈。不用担心,JMM提出了happens-before机制。
happens-before
hanppens-before是JMM对程序员做出的保证的。
在一个线程中前一个操作对后一个操作是可见的
a=3;
a=5;
这种操作是前后有依赖性的。先a赋值为3,后又改成5。不管这两行代码将来重排序后,前后顺序发生如何变化,JMM对程序员做出的保证是a=3先执行,a=5后执行。但是向a=3,b=6; 这种前后没有依赖关系的就不能保证执行顺序了。
同一个对象的解锁操作,对其加锁是可见的
加锁和解锁多发生于多线程中,如果对一个对象进行的解锁操作,那么对这个对象的加锁操作一定是可见的。意思是对一个对象的进行解锁操作,则对该对象进行加锁操作时一定能加锁成功的。
如果A happens-before B,且B happens-before C,那么A happens-before C
volatile字段的写入操作happens-before于每一个后续的同一个字段的读操作