1、可见性:
多线程中,一个线程对状态变量的修改对其它线程可见。
非原子的64位操作
最低安全性:线程在没有同步的情况下读取变量时,可能会得到一个失效的值,但至少这个值是由之前某个线程设置的值,而不是一个随机的值。
最低安全性适用于绝大多数变量,但是存在一个例外,非volatile类型的64位数值变量 double和long
java内存模型要求,变量的读取和写入操作都必须是原子操作但是对非volatile的double和longJVM允许将64位的读或写操作分解位两个32位操作。当读取一个非voila提了类型的long变量时,如果对该变量的都和写在不同的线程中执行,那么狠可能会读取到某个值的高32位和另一个值的低32位。
2、Volatile变量
java提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器活着对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。在访问olatile变量时不会执行枷锁操作,银子也就不会使执行现成阻塞,因此olatile变量是种比sychronized关键字更轻量级的同步机制。
仅当olatile变量能简化代码的实现方式一击对同步策略的验证时,才应该使用他们。如果在验证正确性时需要对可见性进行复杂的判断,那么就不要使用valatile变量。volatile变量的正确使用方式包括:确保他们自身状态的可见性,确保他们所引用对象的状态的可见性,遗迹表示一些重要的程序生命周期时间的发生。
仅当:满足一下所有条件是,才应该使用volatile变量:
- 对变量的写入操作不依赖变量的当前值,活着你能确保只有单个现成更新变量的值。
- 该变量不会与其他状态变量一起纳入不变性条件中
- 在访问变量时不需要加锁。
public class ThisEscape{ public ThisEscape(EventSource eventSource){ source.registerListener(new EventListener(){ public void onEvent(Event e){ doSomething(e); } }); } }这样如果要通过ThisEscape把EventListener注册到EventSource里面的话,就会执行这么一个操作 new ThisEscape(eventSource); 这个操作会返回一个ThisEscape对象。这样的话在发布EventListener的时候,实际也发布了一个ThisEscape对象,那么这个ThisEscape对象就已经逸出了。
书中对this引用逸出是这样解释的:(看书一定要细看,我就犯了这个错误,有部分没注意,结果这个概念纠结了好久) 仅当对象的构造函数返回时,对象才处于一个可预测的和一只的状态,因此当从对象的构造函数中发布对象时,只是发布了一个尚未构造完成的对象。即使发布对象的语句位于构造函数的最后一行也是如此。如果this应用在构造过程中逸出,那么这种对象就被认为是不正确构造。 下面是书中修改后的代码:上
public class SafeListener{ priavte final EventListener listener; private SafeListener(){ listener = new EventListener(){ public void onEvent(Event e){ doSomething(e); } } } public static SafeListener newInstance(EventSource source){ SafeListener safe = new SafeListener(); source.registerListener(safe.listener); return safe; } }上例可以看到需要注册的listener被声明为了final,并且SafeListener的构造方法被声明成了private,然后通过了一个静态的对象工厂来实例化SafeListener和注册这个EventListener;