明确定义线程安全性出人意料地困难,大多数定义看上去完全是自我循环。快速搜索一下 Google,可以找到以下关于线程安全代码的典型的、但是没有多大帮助的定义(或者可以说是描述):
...可以从多个编程线程中调用,无需线程之间不必要的交互。
...可以同时被多个线程调用,不需要调用一方有任何操作。
有这样的定义,就不奇怪我们对于线程安全性会感到如此迷惑。这些定义比说“一个类在可以被多个线程安全调用时就是线程安全的”好不了多少,当然,它的意义就是如此,但是它不能帮助我们区分一个线程安全的类与一个线程不安全的类。安全的意义是什么呢?
实际上,所有线程安全的定义都有某种程序的循环,因为它必须符合类的规格说明 -- 这是对类的功能、其副作用、哪些状态是有效和无效的、不可变量、前置条件、后置条件等等的一种非正式的松散描述(由规格说明给出的对象状态约束只应用于外部可见的状态,即那些可以通过调用其公共方法和访问其公共字段看到的状态,而不应用于其私有字段中表示的内部状态)。
线程安全性
类要成为线程安全的,首先必须在单线程环境中有正确的行为。如果一个类实现正确(这是说它符合规格说明的另一种方式),那么没有一种对这个类的对象的操作序列(读或者写公共字段以及调用公共方法)可以让对象处于无效状态,观察到对象处于无效状态、或者违反类的任何不可变量、前置条件或者后置条件的情况。
此外,一个类要成为线程安全的,在被多个线程访问时,不管运行时环境执行这些线程有什么样的时序安排或者交错,它必须仍然有如上所述的正确行为,并且在调用的代码中没有任何额外的同步。其效果就是,在所有线程看来,对于线程安全对象的操作是以固定的、全局一致的顺序发生的。
正确性与线程安全性之间的关系非常类似于在描述 ACID(原子性、一致性、独立性和持久性)事务时使用的一致性与独立性之间的关系:从特定线程的角度看,由不同线程所执行的对象操作是先后(虽然顺序不定)而不是并行执行的。
方法之问的状态依赖
考虑下面的代码片段,它迭代一个 Vector 中的元素。尽管 Vector 的所有方法都是同步的,但是在多线程的环境中不做额外的同步就使用这段代码仍然是不安全的,因为如果另一个线程恰好在错误的时间里删除了一个元素,则 get() 会抛出一个 ArrayIndexOutOfBoundsException 。
1 Vector v = new Vector();这里发生的事情是: get(index) 的规格说明里有一条前置条件要求 index 必须是非负的并且小于 size() 。但是,在多线程环境中,没有办法可以知道上一次查到的 size() 值是否仍然有效,因而不能确定 i
更明确地说,这一问题是由 get() 的前置条件是以 size() 的结果来定义的这一事实所带来的。只要看到这种必须使用一种方法的结果作为另一种讲法的输入条件的样式,它就是一个 状态依赖,就必须保证至少在调用这两种方法期间元素的状态没有改变。一般来说,做到这一点的唯一方法在调用第一个方法之前是独占性地锁定对象,一直到调用了后一种方法以后。在上面的迭代 Vector 元素的例子中,您需要在迭代过程中同步 Vector 对象。