HeadFirstデザインパターン5章 チョコレートボイラSingleton

HeadFirstデザインパターン5章 は、Singletonパターンです。(架空の会社)Choc-O-Holic社の業務用チョコレートボイラを扱うためのコントローラクラスに、同期化していないSingletonを実装しました。

ところがマルチスレッドを導入したために、ボイラーが大変なことに!

マルチスレッドに対応するために、synchronizedバージョン、クラスロード時インスタンス化バージョン、ダブルチェックドロッキングバージョンを作っていきます。

githubの書籍サンプルコード/singleton

書籍のサンプルコードとは別に、1000スレッドを起動して、Singletonかどうかを確認するmainを作りました。

Main.java最初のバージョン

異なるインスタンスが何個生成されるか、HashMapで集計しました。

private static void simulator(ChocolateBoilerFactory.Type type) { final int N_THREDS = 1000; final CountDownLatch startGate = new CountDownLatch(1); final CountDownLatch endGate = new CountDownLatch(N_THREDS); final Map<Object,Integer> map = new HashMap<Object,Integer>(); for (int i = 0; i < threads.length; i += 1) { final int n = i; Thread thread = new Thread(new Runnable() { @Override public void run() { try { startGate.await(); } catch (InterruptedException e) { e.printStackTrace(); } Object obj = ChocolateBoilerFactory.create(type); map.put(obj, 1); endGate.countDown(); } }); thread.start(); } 〜省略" "); }

ターミナルで実行すると、非同期バージョンは100以上のインスタンスを生成しました。その他のバージョンのインスタンス数は、すべて1のはずですが、2や3がちらほら。

for((i=0; i < 5; i+=1)); do java headfirst.designpatterns.chap5.Main; done 2 3 1 1 1 5 3 1 1 1 5 5 1 3 1 4 3 1 1 2 2 4 1 1 1

何がいけない?と思って調べると、map.entrySet().size()も1ではありませんでした。ところが、map.entrySet()やmap.keySet()をiterator()で表示してみると、内容自体は1個でした。

HashSetを使っても、2や3がちらほらありました。HashMapやHashSetはスレッドセーフではないことが原因なんですね。

HashMapをConcurrentHashMapに置き換えたところ、その他のバージョンのインスタンス数は、すべて1になりました。

スレッド同期化Singletonの実験をしているので、ConcurrentHashMapを使わないようにと考えて、最終的には、スレッド数と同じ長さの配列を用意して、最後にユニーク数を集計することにしました。

Main.java最終バージョン

package headfirst.designpatterns.chap5; import java.util.*; public class Main { public static void main(String[] args) { simulator(ChocolateBoilerFactory.Type.NO_SYNCHRONIZED); simulator(ChocolateBoilerFactory.Type.SYNCHRONIZED); simulator(ChocolateBoilerFactory.Type.INITILIZATION_ON_CLASS_LOADED); simulator(ChocolateBoilerFactory.Type.INITILIZATION_ON_DEMAND_HOLDER); simulator(ChocolateBoilerFactory.Type.DOUBLE_CHECKED_LOCKING); System.out.println(); } private static void simulator(ChocolateBoilerFactory.Type type) { final int N_THREDS = 1000; final CountDownLatch startGate = new CountDownLatch(1); final CountDownLatch endGate = new CountDownLatch(N_THREDS); final Object[] instances = new Object[N_THREDS]; for (int i = 0; i < N_THREDS; i += 1) { final int n = i; Thread thread = new Thread(new Runnable() { @Override public void run() { try { startGate.await(); } catch (InterruptedException e) { e.printStackTrace(); } Object obj = ChocolateBoilerFactory.create(type); instances[n] = obj; endGate.countDown(); } }); thread.start(); } startGate.countDown(); try { endGate.await(); } catch (InterruptedException e) { e.printStackTrace(); } final int n_instances = unique_count(instances); System.out.print(n_instances + " "); } private static int unique_count(Object[] instances) { final Set<Object> set = new HashSet<Object>(); for (int i = 0; i < instances.length; i += 1) { set.add(instances[i]); } return set.size(); } }

ChocolateBoiler.java

abstractにしたり、Factoryクラスを作っていますが、基本的なところは書籍サンプルのままです。今回のMainクラスでは、fill()やdrain()は呼んでいません。

package headfirst.designpatterns.chap5; public abstract class ChocolateBoiler { private boolean empty; private boolean boiled; public ChocolateBoiler() { empty = true; boiled = false; } public void fill() { if (isEmpty()) { empty = false; boiled = false; } } public void drain() { if (!isEmpty() && isBoiled()) { empty = true; } } public void boil() { if (!isEmpty() && !isBoiled()) { boiled = true; } } public boolean isEmpty() { return empty; } public boolean isBoiled() { return boiled; } }

ChocolateBoilerFactory.java

package headfirst.designpatterns.chap5; public class ChocolateBoilerFactory { public enum Type { NO_SYNCHRONIZED, SYNCHRONIZED, INITILIZATION_ON_CLASS_LOADED, INITILIZATION_ON_DEMAND_HOLDER, DOUBLE_CHECKED_LOCKING }; public static ChocolateBoiler create(Type type) { switch (type) { case NO_SYNCHRONIZED: return ChocolateBoilerNoSynchronized.getInstance(); case SYNCHRONIZED: return ChocolateBoilerSynchronized.getInstance(); case INITILIZATION_ON_CLASS_LOADED: return ChocolateBoilerInitializationOnClassLoaded.getInstance(); case INITILIZATION_ON_DEMAND_HOLDER: return ChocolateBoilerInitializationOnDemandHolder.getInstance(); case DOUBLE_CHECKED_LOCKING: return ChocolateBoilerDoubleCheckedLocking.getInstance(); default: throw new IllegalArgumentException(); } } }

ChocolateBoilerNoSynchronized.java

非同期バージョン

package headfirst.designpatterns.chap5; public class ChocolateBoilerNoSynchronized extends ChocolateBoiler { private static ChocolateBoilerNoSynchronized instance; public static ChocolateBoilerNoSynchronized getInstance() { if (instance == null) { instance = new ChocolateBoilerNoSynchronized(); } return instance; } private ChocolateBoilerNoSynchronized() { super(); } }

ChocolateBoilerSynchronized.java

synchronizedバージョン

package headfirst.designpatterns.chap5; public class ChocolateBoilerSynchronized extends ChocolateBoiler { private static ChocolateBoilerSynchronized instance; public synchronized static ChocolateBoilerSynchronized getInstance() { if (instance == null) { instance = new ChocolateBoilerSynchronized(); } return instance; } private ChocolateBoilerSynchronized() { super(); } }

ChocolateBoilerInitializationOnClassLoaded.java

クラスロード時インスタンス化バージョン

package headfirst.designpatterns.chap5; public class ChocolateBoilerInitializationOnClassLoaded extends ChocolateBoiler { private static final ChocolateBoilerInitializationOnClassLoaded instance = new ChocolateBoilerInitializationOnClassLoaded(); public static ChocolateBoilerInitializationOnClassLoaded getInstance() { return instance; } private ChocolateBoilerInitializationOnClassLoaded() { super(); } }

ChocolateBoilerInitializationOnDemandHolder.java

オンデマンドホルダーインスタンス化バージョン

package headfirst.designpatterns.chap5; public class ChocolateBoilerInitializationOnDemandHolder extends ChocolateBoiler { private static class SingletonHolder { private static final ChocolateBoilerInitializationOnDemandHolder instance = new ChocolateBoilerInitializationOnDemandHolder(); } public static ChocolateBoilerInitializationOnDemandHolder getInstance() { return SingletonHolder.instance; } private ChocolateBoilerInitializationOnDemandHolder() { super(); } }

ChocolateBoilerDoubleCheckedLocking.java

ダブルチェックドロッキングバージョン

package headfirst.designpatterns.chap5; public class ChocolateBoilerDoubleCheckedLocking extends ChocolateBoiler { private volatile static ChocolateBoilerDoubleCheckedLocking instance; public static ChocolateBoilerDoubleCheckedLocking getInstance() { if (instance == null) { synchronized (ChocolateBoilerDoubleCheckedLocking.class) { if (instance == null) { instance = new ChocolateBoilerDoubleCheckedLocking(); } } } return instance; } private ChocolateBoilerDoubleCheckedLocking() { super(); } }
タイトルとURLをコピーしました