第四章: オブジェクトを組み立てる
2時就寝・8時起床の見事な寝坊っぷり!
ステートの範囲について
フィールドが全てプリミティブなら、フィールドがそのオブジェクトのステートになる。一方で参照フィールドがあれば、その先のオブジェクトのフィールドも含めてステートになる。
カプセル化
ステートをカプセル化して他のオブジェクトがコンポジットするような構成にすると、カプセル化されたオブジェクトに到達するパスを事前に分析することができ、スレッドセーフにアクセスする方法のみを提供すればスレッドセーフに出来る。
こんな感じ?
public class StateSupport { private final Object lock = new Object(); private final State stateModel; public StateSupport(State state) { this.stateModel = state; } public void updateState(StateType stateType) { synchronized (lock) { stateModel.setState(stateType); } } public StateType getState() { synchronized (lock) { return stateModel.getState(); } } }
可変な状態をカプセル化したStateクラスを作り、このStateに対するアクセス可能性をロックによってスレッドセーフにする。
委譲によってスレッドセーフを実現する
public class Tracker { private final ConcurrentMap<String, Point> locationMap; private final Map<String, Point> unmodifiableMap; public Tracker(Map<String, Point> pointMap) { this.locationMap = new ConcurrentHashMap<String, Point>(pointMap); this.unmodifiableMap = Collections.unmodifiableMap(pointMap); } public Map<String, Point> getLocations() { return unmodifiableMap; } public Point getLocation(String key) { return locationMap.get(key); } public void setLocation(String key, int x, int y) { if (locationMap.replace(key, new Point(x, y)) == null) { throw new IllegalArgumentException("invalid key: " + key); } } }
こんな感じで、全てのステートへのアクセスはConcurrentMapを経由するように変更する。つまり、ConcurrentMapにスレッドセーフ性を委譲している。ここでPointクラスは不可変であることが重要。
CopyOnWriteArrayList
知らなかった。Listのスレッドセーフな実装らしいです。リスナの管理などに良く用いられるとのこと。リスナを収めるリストは普段ArrayListを使って、イベントをfireするときだけスナップショットのインスタンスを作ることが多いけど、CopyOnWriteArrayListを使った方が良いのかな。簡単に実測した感じではCopyOnWriteArrayListの方が早かったです。実装を見ると配列を使ったスナップショットになっていたので、確かに早そうだ。
クライアントサイドロックには適切なロックを使用する
例えばListをコンポジットするヘルパークラスなら、同期化はメソッド単位ではなくListへのアクセスに対して行うべき。
public class ListSupport { private final List<State> list = new ArrayList<State>(); @Deprecated public synchronized boolean putIfAbsent(State state) { boolean absetnt = !list.contains(state); if (absetnt) { list.add(state); } return absetnt; } public boolean putIfAbsent(State state) { synchronized (list) { boolean absetnt = !list.contains(state); if (absetnt) { list.add(state); } return absetnt; } } }