多线程中的可读性问题
在多线程访问同一个对象的变量时,如果read操作和write操作分别在两个不同的线程中,read操作并不能保证马上获取到write操作修改后的值。
因为在没有同步的情况下,编译器、处理器以及运行时都可能对操作的执行顺序进行一些意想不到的调整。在缺乏足够同步的多线程程序中,要想对内存操作的执行顺序进行判断,几乎无法得到正确的结论。
例如以下代码有可能会输出0,并且会无限循环下去。
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;
}
}
这个时候必须使用同步机制才能保证,这也是同步机制的另一个作用,加锁除了保证操作的互斥,还有可见性作用(让其他线程读取到最新的变量值)。
解决方案1:使用synchronized
因此,在javabean中,变量的设置和读取都应该加上synchronized才能保证数据被其他线程立即看到
public class SynchronizedInteger {
private int value;
public synchronized int get() {
return value;
}
public synchronized void set(int value) {
this.value = value;
}
}
一个例子:
public class TT extends Thread {
int b = 100;
public synchronized void m1() {
b = 1000;
try {
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("m1 b=" + b);
}
public synchronized void m2() {
b = 2000;
try {
Thread.sleep(2500);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("m2 b=" + b);
}
public void run() {
try {
m1();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String args[]) {
TT tt = new TT();
tt.start();
tt.m2();
System.out.println(tt.b);
}
}
这时输出
m2 b=2000
2000
m1 b=1000
但如果把m2()方法中的synchronized去掉,则变成
m2 b=1000
1000
m1 b=1000
因为m1和m2的调用是在不同的线程中,因此如果m2没有使用同步,则m2中写入修改的数据不会立即被看到。
另外,在多线程中使用共享可变的64位变量,例如long和double都是不安全的,因为JVM将他们分解成32位原子操作。
解决方案2:使用volatile变量
volatile类型的变量:其他线程可以立即读取到变量的更新,但不加锁。
解决方案3:线程封闭
将变量封闭在一个线程中,从而杜绝多线程安全问题。
或者用final修饰类中的变量,从而实现线程封闭。
另外,使用ThreadLocal类是维持线程封闭的一种更规范的方法。ThreadLocal类用法见另外一张贴。