1. 概要

このチュートリアルでは、JakartaEEで利用可能な2種類のシングルトンを詳しく見ていきます。 違いを説明してデモンストレーションし、それぞれに適した使用法を確認します。

まず、詳細に入る前に、シングルトンとは何かを見てみましょう。

2. シングルトンデザインパターン

シングルトンパターンを実装する一般的な方法は、静的インスタンスとプライベートコンストラクターを使用することです。

public final class Singleton {
    private static final Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}

しかし、残念ながら、これは実際にはオブジェクト指向ではありません。 また、マルチスレッドの問題がいくつかあります。

ただし、CDIおよびEJBコンテナは、オブジェクト指向の代替手段を提供します。

3. CDIシングルトン

CDI(コンテキストと依存性注入)を使用すると、@Singletonアノテーションを使用してシングルトンを簡単に作成できます。 このアノテーションは、javax.injectパッケージの一部です。 コンテナにシングルトンを1回インスタンス化するように指示し、インジェクション中に他のオブジェクトへの参照を渡します。

ご覧のとおり、CDIを使用したシングルトン実装は非常に単純です。

@Singleton
public class CarServiceSingleton {
    // ...
}

私たちのクラスは、自動車サービスショップをシミュレートします。 さまざまなCarのインスタンスがたくさんありますが、それらはすべて同じショップを使用してサービスを提供しています。 したがって、シングルトンが適しています。

クラスのコンテキストを2回要求する単純なJUnitテストを使用して、同じインスタンスであることを確認できます。 読みやすくするために、ここにgetBeanヘルパーメソッドがあることに注意してください。

@Test
public void givenASingleton_whenGetBeanIsCalledTwice_thenTheSameInstanceIsReturned() {       
    CarServiceSingleton one = getBean(CarServiceSingleton.class);
    CarServiceSingleton two = getBean(CarServiceSingleton.class);
    assertTrue(one == two);
}

@Singletonアノテーションがあるため、コンテナは両方の時間で同じ参照を返します。 ただし、プレーンなマネージドBeanでこれを試すと、コンテナーは毎回異なるインスタンスを提供します。

これはjavax.inject.Singletonまたはjavax.ejb.Singletonのどちらでも同じように機能しますが、これら2つの間に重要な違いがあります。

4. EJBシングルトン

EJBシングルトンを作成するには、javax.ejbパッケージの@Singletonアノテーションを使用します。 このようにして、シングルトンセッションBeanを作成します。

この実装は、前の例でCDI実装をテストしたのと同じ方法でテストでき、結果は同じになります。 予想どおり、EJBシングルトンはクラスの単一インスタンスを提供します。

ただし、 EJBシングルトンは、コンテナー管理の同時実行制御の形式で追加機能も提供します。

このタイプの実装を使用する場合、EJBコンテナは、クラスのすべてのパブリックメソッドが一度に1つのスレッドによってアクセスされることを保証します。 複数のスレッドが同じメソッドにアクセスしようとすると、1つのスレッドだけがそのメソッドを使用し、他のスレッドは順番を待ちます。

この動作は、簡単なテストで確認できます。 シングルトンクラスのサービスキューシミュレーションを紹介します。

private static int serviceQueue;

public int service(Car car) {
    serviceQueue++;
    Thread.sleep(100);
    car.setServiced(true); 
    serviceQueue--;
    return serviceQueue;
}

serviceQueue は、車がサービスに「入る」と増加し、サービスが「出る」と減少する単純な静的整数として実装されます。 コンテナによって適切なロックが提供されている場合、この変数はサービスの前後でゼロに等しく、サービス中は1に等しくなければなりません。

簡単なテストでその動作を確認できます。

@Test
public void whenEjb_thenLockingIsProvided() {
    for (int i = 0; i < 10; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                int serviceQueue = carServiceEjbSingleton.service(new Car("Speedster xyz"));
                assertEquals(0, serviceQueue);
            }
        }).start();
    }
    return;
}

このテストは、10個の並列スレッドを開始します。 各スレッドは車をインスタンス化し、サービスを試みます。 サービスの後、serviceQueueの値がゼロに戻ったことを表明します。

たとえば、CDIシングルトンで同様のテストを実行すると、テストは失敗します。

5. 結論

この記事では、JakartaEEで利用可能な2種類のシングルトン実装について説明しました。 それらの長所と短所を確認し、それぞれをいつどのように使用するかも示しました。

そして、いつものように、完全なソースコードはGitHub利用できます。