问题
有这样几种错误的说法:
- 通过查看文档是否出现synchronized修饰符,来判断当前方法是否是安全的。这种说话的错误在于,synchronized并不会通过javadoc输出,成为api文档的一部分,这是因为synchronized是方法具体的实现细节,并不属于导出API和外界模块通信的一部分;
- “只要是加了synchronized关键字的方法或者代码块就一定是线程安全的,而没有加这个关键字的代码就不是线程安全的”。这种观点将synchronized于线程安全等同起来,并且认为线程安全只有两种极端的情况,要么是线程安全的,要么是线程不安全的。
这是两种普遍错误的观点,事实上,线程安全性是有多种级别的,那么,应该如何建立线程安全性的文档?
答案
线程的安全性级别:
不可变的(Immutable):类的实例不可变(不可变类),一定线程安全,如String、Long、BigInteger等。
无条件的线程安全(Unconditionally ThreadSafe):该类的实例是可变的,但是这个类有足够的的内部同步。所以,它的实例可以被并发使用,无需任何外部同步,如Random和ConcurrentHashMap。
有条件的线程安全(Conditionally ThreadSafe):某些方法需要为了线程安全需要在外部使用的时候进行同步。如Collection.synchronized返回的集合,对它们进行迭代时就需要外部同步。如下代码,当对synchronizeColletcion返回的 collection进行迭代时,用户必须手工在返回的 collection 上进行同步,不遵从此建议将导致无法确定的行为:
Collection c = Collections.synchronizedCollection(myCollection); synchronized(c) { Iterator i = c.iterator(); // Must be in the synchronized block while (i.hasNext()) foo(i.next()); }
非线程安全(UnThreadSafe):该类是实例可变的,如需安全地并发使用,必须外部手动同步。如HashMap和ArrayList;
线程对立的(thread-hostile):即便所有的方法都被外部同步保卫,这个类仍不能安全的被多个线程并发使用。这种类或者方法非常少,比如System.runFinalizersOnExit方法是线程队里的,但已经废除了。
在文档中描述有条件的线程安全类要特别小心,必须指明哪个调用方法需要外部同步,并且需要获得哪一把锁;
如果使用类使用的是“一个可公有访问的锁对象”的话,很可能被其他线程超时地保持公有可访问锁,而造成当前线程一直无法获得锁对象,这种行为被称为“拒绝服务攻击”,为了避免这种攻击可以采用 私有锁对象,例如:
private final Object lock = new Object(); public void foo(){ synchronized(lock){ ... } }
这时,私有锁对象只能被当前类内部访问到,并不能被外部访问到,因此不可能妨碍到当前类的同步,就可以避免“拒绝服务攻击”。但是,这种方式只适合“无条件线程安全”级别,并不能适用于“有条件性的线程安全”的级别,有条件的线程安全级别,必须在文档中说明,在调用方法时应该获得哪把锁。
总结
每个类都应该利用严谨的说明或者线程安全注解,清楚地在文档中说明它的线程安全属性。有条件的线程安全类,应该说明哪些方法需要同步访问,以及获得哪把锁。无条件的线程安全类可以采用私有锁对象来防止“拒绝服务攻击”。涉及到线程安全的问题,应该严格按照规范编写文档。