82.スレッドセーフティについてドキュメントすべし
-
提供するメソッドを並列実行させた時の挙動は、クライアントとの重要な約束事であり、ドキュメントとして明記しないとエラーを招くこととなる。
-
sychronized句の有無でスレッドセーフであるかを見分けることはできない。
-
スレッドセーフという性質はオールオアナッシングなものではなく、数段階のレベルがあるものである。以下、スレッドセーフの各レベルを要約する。
- Immutable:状態は常に一貫しており、外部での同期は必要ない。
ex)String,Long,BigInteger - Unconditionally thread-safe:mutableなインスタンスであるが、内部で十分に同期がとれているので、外部での同期は必要ない。
ex)AtomicLong,ConcurrentHashMap - Conditionally thread-safe:Unconditionally thread-safeに似ているが、一部のメソッドでは外部での同期が必要となる。
ex)Collections.synchronized のラッパークラス。これらのiterators は外部の同期が必要となる。 - Not thread-safe:並列処理をしようと思ったら必ず外部で同期が必要となる。
ex)ArrayList,HashMap - Thread-hostile:外部で同期をかけてもスレッドセーフにならない。たいていはstaticなデータを同期なしで変更していることによる。
ex)Item78のgeneralSerialNumber メソッド
- Immutable:状態は常に一貫しており、外部での同期は必要ない。
-
クラスのスレッドセーフティについてはクラスのJavadocに書くべきで、スレッドセーフティに関して特別な考慮が必要なメソッドには、そのJavadocに記述する。以下の
Collections.synchronizedMap
が好例。
/**
* It is imperative that the user manually synchronize on the returned
* map when traversing any of its collection views via {@link Iterator},
* {@link Spliterator} or {@link Stream}:
* <pre>
* Map m = Collections.synchronizedMap(new HashMap());
* ...
* Set s = m.keySet(); // Needn't be in synchronized block
* ...
* synchronized (m) { // Synchronizing on m, not s!
* Iterator i = s.iterator(); // Must be in synchronized block
* while (i.hasNext())
* foo(i.next());
* }
* </pre>
*/
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
return new SynchronizedMap<>(m);
}
- Unconditionally thread-safe なクラスを書くにあたっては、synchronized なメソッドの代わりにprivate lockを使う手法がある。これによって、クライアント、サブクラスからの悪意ある同期から守ることができる。(DOSの例を挙げていたが、どうやって起こすのかいまいちわからない)