本章将介绍如何共享和发布对象,从而使他们能够安全地由多个线程访问。
3.1 可见性
当读操作和写操作在不同的线程中执行时,我们无法确保执行读操作的线程能适时的看到其他线程写入的值,有时甚至是根本不可能的事。为了确保多个线程之间对写操作的可见性,必须引入同步机制。
实例代码 (┬_┬):
package chapter3;
//因为代码中没有引入同步机制,所以无法保证主程序写入的ready和number被读线程读入
public class NoVisibility {
public static boolean ready;
public static int number;
public static class readThread extends Thread{
public void run(){
while(!ready){
Thread.yield();
}
System.out.println(number);
}
}
public static void main(String[] args) {
new readThread().run();
ready = true;
number = 42;
}
}
因为代码中没有引入同步机制,所以无法保证主程序写入的ready和number被读线程读入。
解决办法:只要有数据在多个线程间共享,就使用正确的同步。
3.1.1 失效数据
这段代码是非线程安全的,因为set和get都是在没有同步的情况下访问value的。与其他问题相比,失效值问题更容易出现:
如果某个线程调用了set,另一个调用get函数的线程可能能看到更新后的value,也可能看不到。
3.1.2 非原子的64位操作
但保证至少这个值是之前某个线程设置的值,而不是一个随机值,这种安全性保证也称最低安全性。其适用于绝大多数变量,但是非volatile64位数值变量(double和long)是例外。JVM要求变量的读取和写入操作都必须是原子操作。但是对于非volatile64位变量来说,其分为两部分32位分别读取。所以很可能出现读取某个值的高32位和另外一个值的低32位。除非用volatile声明他们或者用锁保护他们。
3.1.3 加锁与可见性
加锁的含义不止是互斥行为,还包括内存可见性。
为了确保所有线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程都必须在同一个锁上同步。