首先说一个错误的说法:通过查看文档中是否出现synchronized修饰符,可以确认一个方法是否是线程安全的。线程安全性不是一种“要么全有要么全无”的属性。实际上,线程安全性有多种级别。针对常见的情形作简单概括:
--不可变的(immutable) -这个类的实例是不可变的,不需要外部同步。例如String、long和Biginteger
--无条件的线程安全(unconditionally thread-safe) -这个类的实例是可变的,但是这个类有足够的内部同步,它的实例可以被并发使用,无需任何外部同步。例如Random和ConcurrentHashMap
--有条件的线程安全(conditionally thread-safe) -对于单独的操作可以是线程安全的,但是某些操作序列可能需要外部同步。条件线程安全的最常见的例子是遍历由 Hashtable 或者 Vector 或者返回的迭代器。由这些类返回的 fail-fast 迭代器假定在迭代器进行遍历的时候底层集合不会有变化。为了保证其他线程不会在遍历的时候改变集合,进行迭代的线程应该确保它是独占性地访问集合以实现遍历的完整性。通常,独占性的访问是由对锁的同步保证的。并且类的文档应该说明是哪个锁(通常是对象的内部监视器(intrinsic monitor))。
--非线程安全(not thread-safe) - 这个类的实例是可变的。为了并发地使用它们,必须利用外部同步包围每个方法调用。例如ArrayList和hashMap。
--线程对立的(thread-hostile) - 即使所有的方法调用都被外部同步包围,这个类仍不能安全地被多个线程并发使用。线程对立的类或者方法非常少,当类修改静态数据,而静态数据会影响在其他线程中执行的其他类的行为,这时通常会出现线程对立。线程对立类的一个例子是调用 System.setOut() 的类。
二、注意:
1.有条件的线程安全类,必须指明哪个方法调用序列需要外部同步,以及在执行这些序列的时候要获得哪把锁。
2.无条件的线程安全类,应该考虑使用私有锁对象(private lock object)来代替同步的方法:
private final Object lock = new Object();
public void foo() {
synchronized(lock) {
……
}
}
因为这个私有锁对象不能被这个类的客户端程序访问,所以它们不可能妨碍对象的同步。
总结:
每个类都应该利用严谨的说明或者线程安全注解,清楚地在文档中说明它的线程安全属性。synchronized修饰符与这个文档毫无关系。有条件的线程安全类和无条件的线程安全类应该按照上述规范编写实现文档。这样可以防止客户端程序和子类的不同步干扰,让你能够在后续的版本中灵活地对并发控制采用更加复杂的方法。