可见性
单线程,写入读取,很自然。
多线程,当一个线程写入数据,一个读取时候,会产生一系列问题,无法确保读操作的线程看到其他线程写入的数据,因此,需要使用同步。
public classNovisibility{
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;
}
}
这个程序可能有多个结果,可能会输出42,可能会输出0或者无法终止,,因为可能读线程一直被调度,一直循环。也可能读线程先看到了read 的值,但是number的赋值在编译的时候执行了重排序(详见Java内存模型),在ready赋值之后执行,导致此时number为0被输出。
避免方法 只要有数据再多个线程之间共享,就要使用正确的同步。
失效数据
典型的get/set方法在不同步的情况下就比容易发生。
非原子的64位操作
Java内存模型中对于非volatile的long和double变量,JVM允许将64位的读操作或写操作分解为两个32位的操作,当这两种变量在不同的线程中执行时候,可能会读取到某个高32位和另一个低32位,因此,即使不考虑失效数据,多线程使用共享且可变的long和double等类型也是不安全的,除非用volatile或者加锁。
加锁与可见性
内置锁可以用于确保某个线程查看另一个线程的执行结果。当线程A执行某个同步代码块时候,线程B进入同一个锁保护的 同步代码块。可以保证,锁释放之前,B可以看到A在同一个同步代码块的所有执行结果,保证了多线程的可见性。
加锁的含义不仅仅在于局限于互斥行为,还包括内存可见性。为了所有线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程必须在同一个锁上同步。
Volatile变量
稍弱的同步机制,将对象的更新操作通知其他线程,编译器和运行时不会进行重排序,不会被缓存在寄存器或者其他处理器不可见的地方,因此读取volatile变量总会返回最新写入的值。
仅当volatile变量能简化代码的实现以及对同步策略的验证时候,才应该使用。如果验证正确性时需要对可见性进行复杂的判断,那么就不要使用volatile变量。正确使用方式:确保自身状态的可见性,确保引用状态的可见性,以及标识一些重要生命周期时间的发生(初始化或者关闭)
例如
volatile boolean asleep;
...
while( !asleep ){
couneSomeSheep();
注意 volatile不足以保证递增操作,i++
加锁机制保证可见性,原子性,volatile只保证可见性。
使用条件
- 写入操作不依赖当前值,或保证单线程写入
- 访问变量不需要加锁。
- 与其他变量均属于不变性条件。