3.volitile关键字
之前我们说了,线程并不会直接操作主内存的变量,而是操作工作内存中拷贝的变量副本。理解了这一点后,思考下面代码是有限操作还是无限操作
public class demo6 { private static int a = 0; public static void main(String[] args) throws InterruptedException { new Thread(() -> { while(a == 0) {} System.out.println( "thread finished"); }).start(); Thread.sleep(1000); System.out.println("edit var a..."); a = 1; } }
答案是无限操作,为什么呢?当然因为新建线程中使用的不过是工作缓存中的副本咯。
现在我们把代码稍微修改下。您觉得是有限操作还是无限操作呢?
public class demo6 { private static int a = 0; public static void main(String[] args) throws InterruptedException { new Thread(() -> { while (a == 0) { System.out.println( "a = " + a); } }).start(); Thread.sleep(1000); System.out.println("edit var a..."); a = 1; } }
答案是有限操作,不信您可以自己试试看。为什么呢?不妨看看System.out.println()
方法的源码
public void println(String x) { synchronized (this) { print(x); newLine(); } }
原来使用了synchronized关键字呀,除了保证操作的原子性外,还可以保证可见性.线程加锁时将清空工作内存的变量值,线程解锁前,将会把变量的最新值更新到主内存中,因此,它就可以看到最新的a的值为1,退出程序了。
除了synchronized,我们更多使用volitile关键字来实现可见性。
使用volitile修饰变量a即可
public class demo6 { private static volatile int a = 0; public static void main(String[] args) throws InterruptedException { new Thread(() -> { while (a == 0) { } System.out.println("thread execute finished..."); }).start(); Thread.sleep(1000); System.out.println("edit var a..."); a = 1; } }
volitile修饰变量为什么可以保证可见性呢?与sychoronized加锁会让工作空间变量失效不同,它是通过禁止指令重排序实现的。其底层原理是通过内存屏障实现,它可以保证其修饰的变量在进行读写操作时,之前的指令已经执行完,之后的指令还未执行,即操作volitile修饰变量的语句相对位置不会发生改变。
4.happens-before原则
前面我们已经了解了指令重排序的优缺点,JVM提出了happens-before(先行发生)原则,确保程序员只要按照原则编程,就能保证并发编程的正确性。
同一个线程内,前面操作happens-before后面的操作。即使可能出现指令重排,但是前面语句对于变量的修改一定对后续操作可见。
对于一个锁的解锁操作,happens-before对于这把锁后续的加锁操作。即前一个线程解锁后,后面线程都能看到该锁对于变量修改的结果,实际上,我们前面的举例就说了,synchorinized关键字在解锁时会把变量更新到主内存中。
volitile变量的写操作happens-before后续对这个变量的读操作。
线程启动原则。线程A启动线程B,在线程B中可以看到线程B启动前线程A的操作。
线程加入原则。线程A执行过程中join线程B,并成功返回。则线程B的操作happens-before线程A。
传递性规则。如果Ahappens-beforeB,Bhappens-beforeC,则Ahappens-beforeC.
上面的原则如果理解了就很容易掌握。基于上述原则,看如下代码。
public class Demo7 { private static int a = 0; private static int b = 0; public static void main(String[] args) { a = 10; b = a + 1; new Thread(() -> { if(b > 10) { System.out.println(a); } } ).start(); } }
答案是10.