可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉。在单线程环境中,如果向某个变量先写入值,然后在没有其他写入操作的情况下读取这个变量,那么总能得到相同的值。这看起来很自然。 然而,当读操作和写操作在不同的线程中执行时,情况却并非如此。
public class NoVisiaility {
private static boolean ready;
private static int number;
private static class ReaderThread extends Thread {
public void run() {
while (!ready)
Thread.yield();
System.out.println(number);
}
}
/**
* @param args
*/
public static void main(String[] args) {
new ReaderThread().start();
number = 42;
ready = true;
}
}
在上面程序中,主线程和读线程都访问共享变量ready和number。主线程启动读线程,然后将number设为42,并将ready设为true。读线程一直循环直到发现ready的值变为true,然后输出number的值。虽然NoVisiaility看起来会输出42,但事实上很可能输出0,或根本无法终止。这是因为在代码中没有使用足够的同步机制,因此无法保证主线程写入的ready值和number值对于读线程来说是可见的。
NoVisiaility可能会持续循环下去,因为读线程可能永远都看不到ready的值。一种更奇怪的现象时,NoVisiaility可能会输出0,因为读线程可能看到了写入ready的值,但却没有看到之后写入number的值,这种现象被称为“重排序(Reorderiing)”。只要在某个线程中无法检测到重排序情况(即使在其他线程中可以很明显地看到该线程中的重排序),那么就无法确保线程中的操作将按照程序中指定的顺序来执行。当主线程手写如number,然后在没有同步的情况下写入ready,那么读线程看到的顺序可能与写入的顺序完全相反。
在没有同步的情况下,编译器、处理器以及运行时等都可能对操作的执行顺序进行一些意想不到调整。在缺乏足够同步的多线程程序中,要想对内存操作的执行顺序进行判断,几乎无法得到正确的结论。一种简单的方法能够避免这些复杂的问题:只要有数据在多个线程之间共享,就使用正确的同步。