要编写正确的并发程序,关键问题在于:在访问共享的可变状态时需要进行正确的管理。我们知道同步代码块和同步方法可以确保以原子的方式进行操作,但一种常见的误解是,认为关键字synchronized只能用于实现原子性或者确定“临界区”。然而,同步还有另一重要的用途:内存可见性。重点来了:我们不仅希望防止某个线程正在使用对象状态而另一线程在同时修改状态,而且希望确保当一个线程修改了对象状态以后,其他线程都能够看到发生的状态变化。(如果没有同步,是不能实现这样的要求的)。下面就介绍介绍可见性:
在单线程环境中,如果向某个变量先写入值,然后在没有其他写入操作的情况下读取这个变量,那么总能得到相同的值。但是,重点来了,当读操作和写操作在不同的线程中执行时,情况却并非如此,有点懵是吧,来解释解释,我们无法确保执行读操作的线程能实时的看到其他线程对该变量写入的值。为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。
下面的程序执行结果应该不会是你们所想像的,每个主机上执行该段程序的结果可能会不一样,这边的结果是,程序压根进不了run()方法中的循环,因为再启动线程后,main函数所在的主线程就会将ready设为true,所以循环条件不成立,所以控制台也不会有所输出。当然有些主机上会出现无线循环打印的情况,这就是内存可见性带来的问题(主线程做的ready=true操作,在先开的线程中么有读到,所以循环会一直持续。)造成这类现象的原因就是“重排序“,就是操作的顺序无法按照程序中指定的顺序来执行。解决此类现象的方法就是:正确的同步策略。
package com.zy.charter3;
public class NoVisibility {
private static boolean ready = false;
private static int number;
private static class ReaderThread extends Thread {
public void run () {
System.out.println("bbb");
while (!ready) {
Thread.yield();
System.out.println(number);
}
}
}
public static void main(String[] args) {
System.out.println("aaa");
new ReaderThread().start();
ready = true;
number = 42;
System.out.println("ccc");
}
}
多线程取值或者设置值的时候不采用同步比如以下代码,是会出现内存可见性带来的数据不一致的问题(也就是失效数据)
// NotThreadSafe
public class NotThreadSafeDemo {
private int value;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
那么采用以下简单的同步即可化解这样的问题,简单将方法置为同步即可确保对于赋值操作的内存可见性。
class ThreadSafeDemo {
private int value;
public synchronized int getValue() {
return value;
}
public synchronized void setValue(int value) {
this.value = value;
}
}