1. 序章

MapMaker は、Guavaのビルダークラスであり、スレッドセーフなマップを簡単に作成できます。

Javaは、キーに Weak References を使用するために、WeakHashMapをすでにサポートしています。 ただし、値に同じものを使用するためのすぐに使用できるソリューションはありません。 幸いなことに、MapMakerは、キーと値の両方にWeakReferenceを使用するための単純なビルダーメソッドを提供します

このチュートリアルでは、 MapMaker を使用すると、複数のマップを簡単に作成し、弱参照を使用する方法を見てみましょう。

2. Mavenの依存関係

まず、 Google Guava 依存関係を追加しましょう。これは、 MavenCentralで利用できます。

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre</version>
</dependency>

3. キャッシングの例

サーバーがユーザー用にいくつかのキャッシュ(セッションキャッシュとプロファイルキャッシュ)を維持している単純なシナリオを考えてみましょう。

セッションキャッシュは短命であり、ユーザーがアクティブでなくなった後、そのエントリは無効になります。 したがって、ユーザーオブジェクトがガベージコレクションされた後、キャッシュはユーザーのエントリを削除できます。

ただし、プロファイルキャッシュは、存続可能時間(TTL)が長くなる可能性があります。 プロファイルキャッシュのエントリは、ユーザーがプロファイルを更新した場合にのみ無効になります。

この場合、キャッシュは、プロファイルオブジェクトがガベージコレクションされている場合にのみエントリを削除できます。

3.1. データ構造

これらのエンティティを表すクラスを作成しましょう。

まず、ユーザーから始めます。

public class User {
    private long id;
    private String name;

    public User(long id, String name) {
        this.id = id;
        this.name = name;
    }

    public long getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

次に、セッション:

public class Session {
    private long id;

    public Session(long id) {
        this.id = id;
    }

    public long getId() {
        return id;
    }
}

そして最後にプロファイル:

public class Profile {
    private long id;
    private String type;

    public Profile(long id, String type) {
        this.id = id;
        this.type = type;
    }

    public long getId() {
        return id;
    }

    public String getName() {
        return type;
    }

}

3.2. キャッシュの作成

makeMap メソッドを使用して、セッションキャッシュ用のConcurrentMapのインスタンスを作成しましょう。

ConcurrentMap<User, Session> sessionCache = new MapMaker().makeMap();

返されたマップでは、キーと値の両方にnull値を使用することはできません。

次に、プロファイルキャッシュ用にConcurrentMapの別のインスタンスを作成しましょう。

ConcurrentMap<User, Profile> profileCache = new MapMaker().makeMap();

キャッシュの初期容量を指定していないことに注意してください。 したがって、MapMakerはデフォルトで容量16のマップを作成します。

必要に応じて、initialCapacityメソッドを使用して容量を変更できます。

ConcurrentMap<User, Profile> profileCache = new MapMaker().initialCapacity(100).makeMap();

3.3. 同時実行レベルの変更

MapMaker は、同時実行レベルのデフォルト値を4に設定します。 ただし、 sessionCache は、スレッドの競合なしに、より多くの同時更新をサポートする必要があります。

ここで、 concurrencyLevelbuilderメソッドが役に立ちます。

ConcurrentMap<User, Session> sessionCache = new MapMaker().concurrencyLevel(10).makeMap();

3.4. 弱参照の使用

上で作成したマップは、キーと値の両方に強力な参照を使用しています。 そのため、キーと値がガベージコレクションされた場合でも、エントリはマップに残ります。 代わりに弱参照を使用する必要があります。

キー(ユーザーオブジェクト)がガベージコレクションされた後、sessionCacheエントリは無効になります。 したがって、キーに弱参照を使用しましょう。

ConcurrentMap<User, Session> sessionCache = new MapMaker().weakKeys().makeMap();

profileCache の場合、値に弱参照を使用できます。

ConcurrentMap<User, Profile> profileCache = new MapMaker().weakValues().makeMap();

これらの参照がガベージコレクションされる場合、Guavaは、これらのエントリがマップ上の後続の読み取りまたは書き込み操作のいずれにも含まれないことを保証します。 ただし、 size()メソッドは一貫性がない場合があり、これらのエントリを含めることができます。

4. MapMaker内部

MapMaker 弱参照が有効になっていない場合、デフォルトでConcurrentHashMapを作成します 。 等式チェックは、通常の等式メソッドを介して行われます。

弱参照を有効にすると、MapMakerはハッシュテーブルのセットで表されるカスタムマップを内部で作成します。 また、ConcurrentHashMapと同様のパフォーマンス特性を共有します。

ただし、 WeakHashMap との重要な違いは、同一性チェックがID(==および IdentityHashCode )の比較を介して行われることです。

5. 結論

この短い記事では、MapMakerクラスを使用してスレッドセーフなマップを作成する方法を学びました。 また、弱参照を使用するようにマップをカスタマイズする方法も確認しました。

いつものように、記事の完全なソースコードは、GitHubから入手できます。