Java同時プログラミング-拡張スレッドセキュリティクラスについて考える
5232 ワード
再利用は、開発とテスト(テストが自分で厳密に測定するよりも多い)を行う時間とその他のコストを節約します.
しかし,1つのスレッドセキュリティクラスを拡張する際には,いくつかの問題を考える必要がある.
たとえば、すべての公開メソッドにsynchronized修飾を提供し、アクセス反発と可視性を保証するスレッドセキュリティクラスVectorについてよく知られています.
しかし、Vectorは公有的な構造であり、顧客コードの不変性制約について何も知らない.
たとえば、クライアント・コードでVectorオブジェクトに対して2回連続してメソッドが呼び出されます.毎回スレッドが安全ですが、この複合操作は原子操作ではありません.これは私たちの不変性制約を満たさない可能性があります.スレッド・セキュリティ・クラスは「安全ではありません」になります.
データ構造クラスではput-if-absent操作をよく行います.
もちろん、スタックが閉じている場合や単一スレッドアプリケーションの場合は問題ありません.
しかし、マルチスレッドが同じデータ構造オブジェクトにアクセスする場合、この操作が安全かどうかを考慮する必要があります.
いわゆるスレッドセキュリティクラスを使用していても.
このスレッド・セキュリティ・クラスを再利用し、拡張し、不変性制約が破壊されないことを保証します.
JDKの中のVectorを拡張しているので、安心感を与えるかもしれません(一部の仕様で定義されている同期戦略を除いては、誰も保証できません).
しかし、あなたの同僚が提供したスレッドセキュリティクラスなら?次のバージョンで変更されないことは誰も保証できません.
残念なことに、次のバージョンで変化するのは同期ポリシーであり、サブクラスが直接影響を受けることになります.
継承しない場合、既存のスレッドセキュリティクラスを再利用した場合に、独自の不変性制約を保証する方法を考える必要があります.
そこで必要な方法だけを考えて、継承が危険である以上、スレッドセキュリティオブジェクトをfieldとして、必要な機能を持ってきて使えばいいと思います.
しかし、次はエラーの例です.
注意が足りなければ、最初の反応は「大丈夫かな...」
確かに、私たちが使っているリストはCollectionsを使っています.ysnchronizedListは装飾されています.まして私たちが提供したputIfAbsentにもsynchronizedキーワードが付いています.この方法は確かに同期しています.
しかし、あるスレッドがputIfAbsentを呼び出すと、別のスレッドが他のメソッドを呼び出すこともできます.
このように私たちの不変性制約は破壊され、このhelperクラスは意味がありません.
つまり問題はこのsynchronizedであり、synchronized(this)ではなくsynchronized(list)が必要です.
だからhelperは次のように変更する必要があります.
ここまではいいですが、派生クラスとベースクラスにはいくつかの結合があります.
リストをprivate finalと宣言し、コンストラクタでCollectionsを使用するコンストラクタを提供することができます.ysnchronizedListで飾ります.
しかし,それでも挙動上の結合が存在し,ベースクラスの挙動にレンガを添えることはできない.
この問題を解決するには、組み合わせを使用します.
しかし,1つのスレッドセキュリティクラスを拡張する際には,いくつかの問題を考える必要がある.
たとえば、すべての公開メソッドにsynchronized修飾を提供し、アクセス反発と可視性を保証するスレッドセキュリティクラスVectorについてよく知られています.
しかし、Vectorは公有的な構造であり、顧客コードの不変性制約について何も知らない.
たとえば、クライアント・コードでVectorオブジェクトに対して2回連続してメソッドが呼び出されます.毎回スレッドが安全ですが、この複合操作は原子操作ではありません.これは私たちの不変性制約を満たさない可能性があります.スレッド・セキュリティ・クラスは「安全ではありません」になります.
データ構造クラスではput-if-absent操作をよく行います.
もちろん、スタックが閉じている場合や単一スレッドアプリケーションの場合は問題ありません.
しかし、マルチスレッドが同じデータ構造オブジェクトにアクセスする場合、この操作が安全かどうかを考慮する必要があります.
いわゆるスレッドセキュリティクラスを使用していても.
このスレッド・セキュリティ・クラスを再利用し、拡張し、不変性制約が破壊されないことを保証します.
public class BetterVector <E> extends Vector<E> {
static final long serialVersionUID = -3963416950630760754L;
public synchronized boolean putIfAbsent(E x) {
boolean absent = !contains(x);
if (absent)
add(x);
return absent;
}
}
JDKの中のVectorを拡張しているので、安心感を与えるかもしれません(一部の仕様で定義されている同期戦略を除いては、誰も保証できません).
しかし、あなたの同僚が提供したスレッドセキュリティクラスなら?次のバージョンで変更されないことは誰も保証できません.
残念なことに、次のバージョンで変化するのは同期ポリシーであり、サブクラスが直接影響を受けることになります.
継承しない場合、既存のスレッドセキュリティクラスを再利用した場合に、独自の不変性制約を保証する方法を考える必要があります.
そこで必要な方法だけを考えて、継承が危険である以上、スレッドセキュリティオブジェクトをfieldとして、必要な機能を持ってきて使えばいいと思います.
しかし、次はエラーの例です.
class BadListHelper <E> {
public List<E> list = Collections.synchronizedList(new ArrayList<E>());
public synchronized boolean putIfAbsent(E x) {
boolean absent = !list.contains(x);
if (absent)
list.add(x);
return absent;
}
}
注意が足りなければ、最初の反応は「大丈夫かな...」
確かに、私たちが使っているリストはCollectionsを使っています.ysnchronizedListは装飾されています.まして私たちが提供したputIfAbsentにもsynchronizedキーワードが付いています.この方法は確かに同期しています.
しかし、あるスレッドがputIfAbsentを呼び出すと、別のスレッドが他のメソッドを呼び出すこともできます.
このように私たちの不変性制約は破壊され、このhelperクラスは意味がありません.
つまり問題はこのsynchronizedであり、synchronized(this)ではなくsynchronized(list)が必要です.
だからhelperは次のように変更する必要があります.
class GoodListHelper <E> {
public List<E> list = Collections.synchronizedList(new ArrayList<E>());
public boolean putIfAbsent(E x) {
synchronized (list) {
boolean absent = !list.contains(x);
if (absent)
list.add(x);
return absent;
}
}
}
ここまではいいですが、派生クラスとベースクラスにはいくつかの結合があります.
リストをprivate finalと宣言し、コンストラクタでCollectionsを使用するコンストラクタを提供することができます.ysnchronizedListで飾ります.
しかし,それでも挙動上の結合が存在し,ベースクラスの挙動にレンガを添えることはできない.
この問題を解決するには、組み合わせを使用します.
public class ImprovedList<T> implements List<T> {
private final List<T> list;
public ImprovedList(List<T> list) { this.list = list; }
public synchronized boolean putIfAbsent(T x) {
boolean contains = list.contains(x);
if (contains)
list.add(x);
return !contains;
}
public int size() {
return list.size();
}
public boolean isEmpty() {
return list.isEmpty();
}
public boolean contains(Object o) {
return list.contains(o);
}
public Iterator<T> iterator() {
return list.iterator();
}
public Object[] toArray() {
return list.toArray();
}
public <T> T[] toArray(T[] a) {
return list.toArray(a);
}
public synchronized boolean add(T e) {
return list.add(e);
}
public synchronized boolean remove(Object o) {
return list.remove(o);
}
public boolean containsAll(Collection<?> c) {
return list.containsAll(c);
}
public synchronized boolean addAll(Collection<? extends T> c) {
return list.addAll(c);
}
public synchronized boolean addAll(int index, Collection<? extends T> c) {
return list.addAll(index, c);
}
public synchronized boolean removeAll(Collection<?> c) {
return list.removeAll(c);
}
public synchronized boolean retainAll(Collection<?> c) {
return list.retainAll(c);
}
public boolean equals(Object o) {
return list.equals(o);
}
public int hashCode() {
return list.hashCode();
}
public T get(int index) {
return list.get(index);
}
public T set(int index, T element) {
return list.set(index, element);
}
public void add(int index, T element) {
list.add(index, element);
}
public T remove(int index) {
return list.remove(index);
}
public int indexOf(Object o) {
return list.indexOf(o);
}
public int lastIndexOf(Object o) {
return list.lastIndexOf(o);
}
public ListIterator<T> listIterator() {
return list.listIterator();
}
public ListIterator<T> listIterator(int index) {
return list.listIterator(index);
}
public List<T> subList(int fromIndex, int toIndex) {
return list.subList(fromIndex, toIndex);
}
public synchronized void clear() { list.clear(); }
}