クラシックなSingletonは、オブジェクトのインスタンスが1つだけ作成されることを保証します。Javaにはいくつかの実装方法がありますが、マルチスレッドおよびシリアル化を考慮する場合、次のような注意点があります。
public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
enum EnumSingleton { INSTANCE; // メソッド }
private Object readResolve() { return getInstance(); }
スレッドセーフなSingletonのために、メソッドgetInstance()にsynchronizedを使用するだけで十分ですか?
回答: はいですが、そのアプローチはパフォーマンスを低下させます。なぜなら、synchronizedはgetInstanceへの各呼び出しで呼び出されるからです。より効率的なのは、Double-checked Locking + volatileを使うか、Enumを使用してSingletonを実装することです。
非効率的なコードの例:
public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }
物語
金融システムでSingletonを実装する際に、ダブルチェックロックのinstanceへの参照に
volatileを追加することを忘れました。その結果、高負荷時にクラスの二重インスタンス生成が発生し、報告の一貫性が失われました。
物語
ロギングライブラリでは、プライベートな静的オブジェクトを通じてSingletonを実装しましたが、シリアル化およびその後のデシリアル化(例えば、クラスター環境で)で複数のインスタンスが発生しました。問題はreadResolve()を追加することで解決されました。
物語
分析システムで、開発者はsynchronizedメソッドgetInstance()を通じてスレッドセーフなSingletonを実装しました。高負荷システムでピークトラフィックが発生したとき、パフォーマンスが急激に低下し、getInstance()の呼び出し(1秒間に数千回)が不必要な同期のために互いにブロックされていることが判明しました。初期化は1回だけ必要です。