Java语言中的volatile变量可以认为是“不太同步”;与synchronized块相比,volatile变量需要的编码更少,运行时的开销也更少,但它能实现的功能只是synchronized的一部分。本文介绍了几种有效使用volatile变量的模式,并强调了几种不适合使用volatile变量的情况。锁提供两个主要属性:互斥和可见性。互斥是指在同一时刻只允许一个线程持有特定的锁,因此可以利用这一特性来实现对共享数据的协调访问协议,使得同一时刻只有一个线程可以使用共享数据。可见性稍微复杂一点,它必须保证在释放锁之前对共享数据所做的更改对于随后获取锁的另一个线程是可见的——没有同步机制提供的可见性保证,线程看到共享变量可以有预-修改或不一致的值,这会导致许多严重的问题。Volatile变量Volatile变量具有同步可见特性,但不具有原子特性。这意味着线程可以自动发现volatile变量的最新值。易失性变量可用于提供线程安全,但仅适用于非常有限的一组用例:多个变量之间或变量的当前值和修改值之间没有约束。因此,单独使用volatile不足以实现计数器、互斥锁或任何具有与多个变量相关的不变量(例如“start<=end”)的类。出于简单性或可扩展性的原因,您可能更愿意使用易失性变量而不是锁。当使用volatile变量而不是锁时,某些习语(idioms)更容易编码和阅读。此外,volatile变量不会像锁那样阻塞线程,因此它们很少会造成可伸缩性问题。在某些情况下,如果读取操作远远超过写入操作,volatile变量还可以提供优于锁的性能优势。正确使用volatile变量的条件您只能在有限的几种情况下使用volatile变量代替锁。对于提供理想线程安全性的volatile变量,必须满足以下两个条件:写入变量不依赖于当前值。该变量不包含在与其他变量的不变量中。实际上,这些条件表明可以写入volatile变量的有效值独立于任何程序状态,包括变量的当前状态。第一个条件的限制阻止了volatile变量被用作线程安全计数器。自增操作(x++)虽然看起来是一个单一的操作,但实际上是由一系列读-修改-写操作组成的组合操作,必须以原子方式执行,而volatile不能提供必要的原子特性。实现正确的操作需要在操作过程中保持x的值不变,这是volatile变量无法实现的。(但是,如果将值调整为仅从单线程写入,则第一个条件可以忽略。)大多数编程情况都会与这两个条件中的任何一个发生冲突,使得volatile变量无法像synchronized那样普遍适用实现线程安全。清单1显示了一个非线程安全的数字范围类。它包含一个不变量——下限总是小于或等于上限。清单1.非线程安全数值范围类@NotThreadSafepublicclassNumberRange{privateintlower,upper;publicintgetLower(){返回更低;}publicintgetUpper(){返回上层;}publicvoidsetLower(intvalue){if(value>upper)thrownewIllegalArgumentException(...);较低=价值;}publicvoidsetUpper(intvalue){if(value
