定义线程安全 style=COLOR: # href=http://safeitcom/ target=_blank>安全性
明确定义线程安全性出人意料地困难大多数定义看上去完全是自我循环快速搜索一下 Google可以找到以下关于线程安全代码的典型的但是没有多大帮助的定义(或者可以说是描述)
可以从多个编程线程中调用无需线程之间不必要的交互
可以同时被多个线程调用不需要调用一方有任何操作
有这样的定义就不奇怪我们对于线程安全性会感到如此迷惑这些定义比说一个类在可以被多个线程安全调用时就是线程安全的好不了多少当然它的意义就是如此但是它不能帮助我们区分一个线程安全的类与一个线程不安全的类安全的意义是什么呢?
实际上所有线程安全的定义都有某种程序的循环因为它必须符合类的规格说明 这是对类的功能其副作用哪些状态是有效和无效的不可变量前置条件后置条件等等的一种非正式的松散描述(由规格说明给出的对象状态约束只应用于外部可见的状态即那些可以通过调用其公共方法和访问其公共字段看到的状态而不应用于其私有字段中表示的内部状态)
线程安全性
类要成为线程安全的首先必须在单线程环境中有正确的行为如果一个类实现正确(这是说它符合规格说明的另一种方式)那么没有一种对这个类的对象的操作序列(读或者写公共字段以及调用公共方法)可以让对象处于无效状态观察到对象处于无效状态或者违反类的任何不可变量前置条件或者后置条件的情况
此外一个类要成为线程安全的在被多个线程访问时不管运行时环境执行这些线程有什么样的时序安排或者交错它必须仍然有如上所述的正确行为并且在调用的代码中没有任何额外的同步其效果就是在所有线程看来对于线程安全对象的操作是以固定的全局一致的顺序发生的
正确性与线程安全性之间的关系非常类似于在描述 ACID(原子性一致性独立性和持久性)事务时使用的一致性与独立性之间的关系从特定线程的角度看由不同线程所执行的对象操作是先后(虽然顺序不定)而不是并行执行的
方法之问的状态依赖
考虑下面的代码片段它迭代一个 Vector 中的元素尽管 Vector 的所有方法都是同步的但是在多线程的环境中不做额外的同步就使用这段代码仍然是不安全的因为如果另一个线程恰好在错误的时间里删除了一个元素则 get() 会抛出一个 ArrayIndexOutOfBoundsException
Vector v = new Vector();
// contains race conditions may require external synchronization
for (int i=; i<vsize(); i++) {
doSomething(vget(i));
}
这里发生的事情是 get(index) 的规格说明里有一条前置条件要求 index 必须是非负的并且小于 size() 但是在多线程环境中没有办法可以知道上一次查到的 size() 值是否仍然有效因而不能确定 i
更明确地说这一问题是由 get() 的前置条件是以 size() 的结果来定义的这一事实所带来的只要看到这种必须使用一种方法的结果作为另一种讲法的输入条件的样式它就是一个 状态依赖就必须保证至少在调用这两种方法期间元素的状态没有改变一般来说做到这一点的唯一方法在调用第一个方法之前是独占性地锁定对象一直到调用了后一种方法以后在上面的迭代 Vector 元素的例子中您需要在迭代过程中同步 Vector 对象
线程安全程度
如上面的例子所示线程安全性不是一个非真即假的命题 Vector 的方法都是同步的并且 Vector 明确地设计为在多线程环境中工作但是它的线程安全性是有限制的即在某些方法之间有状态依赖(类似地如果在迭代过程中 Vector 被其他线程修改那么由 Vectoriterator() 返回的 iterator 会抛出 ConcurrentModificationException )
对于 Java 类中常见的线程安全性级别没有一种分类系统可被广泛接受不过重要的是在编写类时尽量记录下它们的线程安全行为
Bloch 给出了描述五类线程安全性的分类方法不可变线程安全有条件线程安全线程兼容和线程对立只要明确地记录下线程安全特性那么您是否使用这种系统都没关系这种系统有其局限性 各类之间的界线不是百分之百地明确而且有些情况它没照顾到 但是这套系统是一个很好的起点这种分类系统的核心是调用者是否可以或者必须用外部同步包围操作(或者一系列操作)下面几节分别描述了线程安全性的这五种类别
不可变
本栏目的普通读者听到我赞美不可变性的优点时不会感到意外不可变的对象一定是线程安全的并且永远也不需要额外的同步因为一个不可变的对象只要构建正确其外部可见状态永远也不会改变永远也不会看到它处于不一致的状态Java 类库中大多数基本数值类如 Integer String 和 BigInteger 都是不可变的
[] []