4.3.1。例:委任を使用した車両追跡
委任のより重要な例として、スレッドセーフなクラスに委任する車両追跡のバージョンを作成してみましょう。場所はMapに保存するため、スレッドセーフなMap実装から始めConcurrentHashMap
ます。MutablePoint
リスト4.6に示すように、の代わりに不変のPointクラスを使用して場所を保存します。
リスト4.6 DelegatingVehicleTrackerによって使用される不変のPointクラス。
class Point{
public final int x, y;
public Point() {
this.x=0; this.y=0;
}
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
Point
不変であるため、スレッドセーフです。不変の値は自由に共有および公開できるため、返すときに場所をコピーする必要はありません。
DelegatingVehicleTracker
リスト4.7では、明示的な同期は使用していません。状態へのすべてのアクセスはによって管理されConcurrentHashMap
、マップのすべてのキーと値は不変です。
リスト4.7 スレッドセーフティをConcurrentHashMapに委任します。
public class DelegatingVehicleTracker {
private final ConcurrentMap<String, Point> locations;
private final Map<String, Point> unmodifiableMap;
public DelegatingVehicleTracker(Map<String, Point> points) {
this.locations = new ConcurrentHashMap<String, Point>(points);
this.unmodifiableMap = Collections.unmodifiableMap(locations);
}
public Map<String, Point> getLocations(){
return this.unmodifiableMap; // User cannot update point(x,y) as Point is immutable
}
public Point getLocation(String id) {
return locations.get(id);
}
public void setLocation(String id, int x, int y) {
if(locations.replace(id, new Point(x, y)) == null) {
throw new IllegalArgumentException("invalid vehicle name: " + id);
}
}
}
MutablePoint
Pointの代わりに元のクラスを使用した場合、getLocations
スレッドセーフではない変更可能な状態への参照を公開させることにより、カプセル化を解除することになります。Vehicle Trackerクラスの動作が少し変更されていることに注意してください。監視バージョンは場所のスナップショットを返しましたが、委任バージョンは変更できませんが、車両の場所の「ライブ」ビューを返します。つまり、スレッドAが呼び出しgetLocations
、スレッドBが後でいくつかのポイントの位置を変更した場合、それらの変更はスレッドAに返されるマップに反映されます。
4.3.2。独立した状態変数
また、基礎となる状態変数が独立している限り、スレッドセーフを複数の基礎となる状態変数に委任することもできます。
VisualComponent
リスト4.9は、クライアントがマウスおよびキーストロークイベントのリスナーを登録できるようにするグラフィカルコンポーネントです。イベントが発生したときに適切なリスナーを呼び出すことができるように、各タイプの登録済みリスナーのリストを維持します。ただし、一連のマウスリスナーとキーリスナーの間に関係はありません。2つは独立しているためVisualComponent
、スレッドセーフの義務を2つの基になるスレッドセーフリストに委任できます。
リスト4.9 スレッドセーフティを複数の基になる状態変数に委任する。
public class VisualComponent {
private final List<KeyListener> keyListeners
= new CopyOnWriteArrayList<KeyListener>();
private final List<MouseListener> mouseListeners
= new CopyOnWriteArrayList<MouseListener>();
public void addKeyListener(KeyListener listener) {
keyListeners.add(listener);
}
public void addMouseListener(MouseListener listener) {
mouseListeners.add(listener);
}
public void removeKeyListener(KeyListener listener) {
keyListeners.remove(listener);
}
public void removeMouseListener(MouseListener listener) {
mouseListeners.remove(listener);
}
}
VisualComponent
を使用しCopyOnWriteArrayList
て各リスナーリストを格納します。これは、リスナーリストの管理(セクション5.2.3を参照)に特に適した、スレッドセーフなList実装です。各リストはスレッドセーフであり、一方の状態をもう一方の状態に結び付ける制約がないためVisualComponent
、スレッドセーフの責任を基になるmouseListeners
とkeyListeners
オブジェクトに委任できます。
4.3.3。委任が失敗した場合
ほとんどの複合クラスは、それほど単純ではありませんVisualComponent
。コンポーネントの状態変数に関連する不変式があります。NumberRange
リスト4.10では、2つAtomicIntegers
を使用して状態を管理していますが、追加の制約を課しています。最初の数が2番目の数以下であることです。
リスト4.10。不変条件を十分に保護しない番号範囲クラス。これを行わないでください。
public class NumberRange {
// INVARIANT: lower <= upper
private final AtomicInteger lower = new AtomicInteger(0);
private final AtomicInteger upper = new AtomicInteger(0);
public void setLower(int i) {
//Warning - unsafe check-then-act
if(i > upper.get()) {
throw new IllegalArgumentException(
"Can't set lower to " + i + " > upper ");
}
lower.set(i);
}
public void setUpper(int i) {
//Warning - unsafe check-then-act
if(i < lower.get()) {
throw new IllegalArgumentException(
"Can't set upper to " + i + " < lower ");
}
upper.set(i);
}
public boolean isInRange(int i){
return (i >= lower.get() && i <= upper.get());
}
}
NumberRange
スレッドセーフではありません。下限と上限を制約する不変式は保持されません。setLower
およびsetUpper
方法は、この不変を尊重しようとしますが、そう悪いください。setLower
とsetUpper
はどちらもcheck-then-actシーケンスですが、アトミックにするために十分なロックを使用していません。数値の範囲が(0、10)であり、あるスレッドがsetLower(5)
を呼び出している間に別のスレッドがを呼び出すsetUpper(4)
場合、不運なタイミングで、両方がセッターのチェックに合格し、両方の変更が適用されます。その結果、範囲が(5、4)になり、無効な状態になります。したがって、基になるAtomicIntegerはスレッドセーフですが、複合クラスはそうではありません。基礎となる状態変数lower
とupper
は独立しておらずNumberRange
、スレッドセーフをスレッドセーフな状態変数に単純に委譲することはできません。
NumberRange
共通のロックでロワーとアッパーをガードするなど、ロックを使用して不変条件を維持することにより、スレッドセーフにすることができます。また、クライアントがその不変条件を覆すことを防ぐために、下限と上限の公開を回避する必要があります。
クラスが複合アクションを持っている場合NumberRange
、デリゲートだけではスレッドセーフに適したアプローチではありません。これらの場合、複合アクション全体が基礎となる状態変数に委任されない限り、クラスは独自のロックを提供して複合アクションがアトミックであることを保証する必要があります。
クラスが複数の独立したスレッドセーフな状態変数で構成されていて、無効な状態遷移を伴う操作がない場合、スレッドの安全性を基になる状態変数に委任できます。