线程安全性的文档化。

当一个了IDE实例或者静态方法被并发使用的时候,这个类的行为如何,是该类与其客户端程序建立的约定的重要组成部分。如果你没有在一个类的文档中描述其行为的并发性情况,使用这个类的程序员将不得不做出某些假设。如果这些假设是错误的,这样得到的程序就可能缺少足够的同步,或者过度同步。无论属于这其中的哪种情况,都可能会发生严重的错误。

你可能听过这样的说法:通过查看文档中是否出现synchronized修饰符,可以确定一个方法是否是线程安全的。这种说法从几个方面来说都是错误的。在正常的操作中,Javadoc并没有在它的输出中包含synchronized修饰符,这是有理由的。因为在一个方法声明中出现synchronized修饰符,这是个实现细节,并不是导出的API的一部分。她并不一定表明这个方法是线程安全的。

而且,“出现了synchronized关键字就足以用文档说明线程安全性”的这种说法隐含了一个错误的观念,即认为线程安全性是一种“要么全有要么全无”的属性。实际上,线程安全性有多种级别。一个类为了可被多个线程安全的使用,必须在文档中清楚地说明他所支持的线程安全性级别。

下面的列表概括了线程安全性的几种级别。这份列表并没有涵盖所有的可能,而只是些常见的情形:

  • 不可变的(immutable)——这个类的实例视不变的。所以,不需要外部的同步。这样的例子包括String、Long和BigInteger。
  • 无条件的线程安全(unconditionally thread-safe)——这个类的实例是可变的,但是这个类有足够的内部同步,所以,它的实例可以被并发使用,无需任何外部同步。其例子包括Random和ConcurrentHashMap。
  • 有条件的线程安全(conditionally thread-safe)——除了有些方法为进行安全的并发使用而需要外部同步之外,这种线程安全级别与无条件的线程安全相同。这样的例子包括Collections.synchronized包装返回的集合,他们的迭代器要求外部同步。
  • 非线程安全(not thread-safe)——这个类的实例是可变的。为了并发的使用他们,客户必须利用自己选择的外部同步包围每个方法调用(或者调用序列)。这样的例子包括通用的集合实现,例如ArrayList和HashMap。
  • 线程对立的(thread-hostile)——这个类不能安全的被多个线程并发使用,即使所有方法调用都被外部同步包围。线程对立的根源通常在于,没有同步的修改静态数据。没有人会有意编写一个线程对立的类;这种类是因为没有考虑到并发性而产生的后果。幸运的是,在Java平台类库中,线程对立的类或方法非常少。System.runFinalizersOnExit方法是线程对立的,但已经被废除了。

在文档中描述一个有条件的线程安全类要特别小心。你必须指明哪个调用序列需要外部同步,还要指明为了执行这些序列,必须获得哪一把锁(极少的情况下是指哪几把锁)。通常情况下,这是指作用在实例自身上的那把锁。但也有例外。如果一个对象代表了另一个对象的一个视图(view),客户通常就必须在后台对象上同步,以防止其他线程直接修改后台对象。

类的线程安全说明通常放在他的文档注释中,但是带有特殊线程安全属性的方法则应该在他们自己的文档注释中说明他们的属性。没有必要说明枚举类型的不可变性。除非从返回类型来看已经很明显,否则静态工厂必须在文档中说明被返回对象的线程安全性。

当一个类承诺了“使用一个公有可访问的所对象”时,就意味着允许客户端以原子的方式执行一个方法调用序列,但是,这种灵活性是要付出代价的。并发集合使用的那种并发控制,并不能与高性能的内部并发控制相兼容。客户端还可以发起拒绝服务攻击,它只需超时的保持公有可访问锁即可。这有可能是无意的,也可能是有意的。

为了避免这种拒绝服务攻击,应该使用一个私有锁对象来代替同步的方法(隐含着一个公有可访问锁)。

重申一下,私有锁对象模式只能用在无条件的线程安全类上。有条件的线程安全类不能使用这种模式,因为他们必须在文档中说明:在执行某些方法调用序列时,他们的客户端程序必须获得哪把锁。

私有锁对象模式特别适用于那些专门为继承而设计的类。如果这种类使用它的实例作为锁对象,子类可能很容易在无意中方案基类的操作,反之亦然。出于不同的目的而使用相同的锁,子类和基类可能会“相互绊住对方的脚”。这不只是一个理论意义上的问题。

简而言之,每个类都应该利用字斟句酌的说明或者线程安全注解,清楚地在文档中说明他的线程安全属性。sychronized修饰符与这个文档毫无关系。有条件的线程安全类必须在文档中指明“哪个方法调用序列需要外部同步,以及在执行这些序列的时候要获得哪把锁”。如果你编写的是无条件的线程安全类,就应该考虑使用私有锁对象来代替同步的方法。这样可以防止客户端程序和子类的不同步干扰,让你能够在后续的版本中灵活的对并发控制采用更加复杂的方法。

猜你喜欢

转载自blog.csdn.net/en_joker/article/details/81428711