学习java并发编程实战的一些心得体会(一)

内存可见性

当读操作和写操作在不同的线程中执行时,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情,为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。

在程序中,NoVisibility说明了当多个线程在没有同步的情况下共享数据时出现的错误。在代码中,主线程和读线程都将访问共享变量ready和number。主线程启动读线程,然后将number设为42,并将ready设为true,读线程一直循环直到发现ready的值变为true,然后输出number的值,虽然NoVisibility看起来会输出42,但事实上很可能输出0,或者根本无法终止。只是因为在代码中没有使用足够的同步机制,因此无法保证主线程写入的ready值和number值对于读线程来说是可见的。


public class NoVisibility {

    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);
        }
    }

    public static void main(String[] args) {
        new ReaderThread().start();
        number = 42;
        ready = true;
    }
}



NoVisibility 可能会持续循环下去,因为读线程可能永远都看不到ready的值。一种更奇怪的现象是,NoVisibility可能会输出0,因为读线程可能看到了写入ready的值,但却没有看到之后写入number的值这种现象被称为“重排序”。只要在某个线程中无法检测到重排序情况,那个就无法确保线程中的操作将按照程序中指定的顺序来执行。当主线程首先写入number,然后在没有同步的情况下写入ready,那么读线程看到的顺序可能与写入的顺序完全相反。


在没有同步的情况下,编译器、处理器、以及运行时等都可以对操作的执行顺序进行一些意想不到的调整。在缺乏足够同步的多线程程序中,要想对内存操作的执行顺序进行判断,几乎无法得出正确的结论

非原子的64位操作

当线程在没有同步的情况下读取变量时,可能会得到一个失效的值,单至少这个值是由之前某个线程设置的值,而不是一个随机值。这种安全性保证也被称为最低安全性。
 
 
最低安全性适用于绝大多数变量,但是存在一个例外:非volatile 类型的64位数值变量(double和long)。Java内存模型要求,变量的读取操作和写入操作都必须是原子操作,但对于非volatile类型的long和double变量,JVM允许将64位的读操作或写操作分解为两个32位操作。当读取一个非volatile类型的long变量时,如果对该变量的读操作和写操作在不同的线程中执行,那么和可能会读到某个值的高32位和另一个值的低32位。因此,即使不考虑失效数据问题,在多线程程序中使用共享且可变的long和double等类型的变量也是不安全的除非用关键字volatile来声明它们,或者用锁保护起来



你可能感兴趣的:(java,多线程)