可见性是Java虚拟机上定义的逻辑上的偏序关系。通常指某些操作的结果对后续的其他操作必须是可见的。详细内容参见《Java Concurrency In Practice》,这部分内容通常被称为Java Memory Model,与操作系统Memory Model概念类似。主要通过一个简单程序说明一下,相当于一个学习总结。下面是程序
参考Doug Lea的JSR133 CookBook http://gee.cs.oswego.edu/dl/jmm/cookbook.html
/** *Not Thread Safe */ public class Counter { private int count = 0; public void increment() { count++; } public int get() { return count; } }
程序很简单,但不是线程安全的问题很多。包括reorder,可见性,还有最基本的互斥执行,都没得到保证
1. increment方法不是原子操作,自增实际上对应多个机器指令。所以并发情况下,会出现不正确结果。
那加上synchronized又如何。
public class Counter { private int count = 0; public synchronized void increment() { count++; } public int get() { return count; } }
2.我知道肯定有人会说get执行时没有加锁,读操作也需要加锁,确实如此,注意这一点,synchronized的操作造成的结果对其他执行相同synchronized操作的线程是可见的,但由于get没有加锁,所以不能保证increment对该get操作可见。假设count是0执行了一次increment之后get到的可能还是0,因为increment的操作造成的结果也许get线程还不能看到。此外还有reordering造成的问题,但是这种情形出现几率很小。有兴趣请去看JSR 133 http://gee.cs.oswego.edu/dl/jmm/cookbook.html
现在看最后一个版本(抱歉不是都加synchronized版本)
public class Counter { private volatile int count = 0; public synchronized void increment() { count++; } public int get() { return count; } }
3.根据brain geotz 同学的说法,上面的代码是线程安全的。
实际上它确实是线程安全的。因为volatile同样有可见性保证,这保证increment操作的结果对所有count的读取操作都是可见的。同样synchronized和volatile在虚拟机上都有reorder的约束,所以可以保证是线程安全的。关于更多的可见性和reorder同样参考JSR133http://gee.cs.oswego.edu/dl/jmm/cookbook.html
注意,这样的客户端调用是不正确的。
class Wrapper{ private Counter counter = new Counter(); public int incrementAndGet(){ counter.increment(); return counter.get(); } }Counter只保证单个get和increment操作是正确,但组合操作是不能保证线程安全的。