1. 概要

コレクションフレームワークは、Javaの主要コンポーネントです。 多数のインターフェースと実装を提供し、さまざまなタイプのコレクションを簡単な方法で作成および操作できるようにします。

単純な非同期コレクションの使用は全体的に単純ですが、マルチスレッド環境(別名 並行プログラミング)。

したがって、Javaプラットフォームは、Collectionsクラス内に実装されたさまざまな同期ラッパーを通じてこのシナリオを強力にサポートします。

これらのラッパーを使用すると、いくつかの静的ファクトリメソッドを使用して、提供されたコレクションの同期ビューを簡単に作成できます。

このチュートリアルでは、 これらの静的同期ラッパーについて詳しく見ていきます。 また、同期コレクションと同時コレクションの違いについても説明します。

2. synchronizedCollection()メソッド

このまとめで取り上げる最初の同期ラッパーは、 synchronizedCollection()メソッドです。 名前が示すように、は、指定されたコレクションによってバックアップされたスレッドセーフなコレクションを返します。

ここで、このメソッドの使用方法をより明確に理解するために、基本的な単体テストを作成しましょう。

Collection<Integer> syncCollection = Collections.synchronizedCollection(new ArrayList<>());
    Runnable listOperations = () -> {
        syncCollection.addAll(Arrays.asList(1, 2, 3, 4, 5, 6));
    };
    
    Thread thread1 = new Thread(listOperations);
    Thread thread2 = new Thread(listOperations);
    thread1.start();
    thread2.start();
    thread1.join();
    thread2.join();
    
    assertThat(syncCollection.size()).isEqualTo(12);
}

上に示したように、このメソッドを使用して提供されたコレクションの同期ビューを作成するのは非常に簡単です。

メソッドが実際にスレッドセーフなコレクションを返すことを示すために、最初にいくつかのスレッドを作成します。

その後、Runnableインスタンスをラムダ式の形式でコンストラクターに挿入します。 Runnable は関数型インターフェースであるため、ラムダ式に置き換えることができることに注意してください。

最後に、各スレッドが同期されたコレクションに6つの要素を効果的に追加することを確認するだけなので、最終的なサイズは12になります。

3. synchronizedList()メソッド

同様に、 synchronizedCollection()メソッドと同様に、 synchronizedList()ラッパーを使用して、同期されたListを作成できます。

予想どおり、メソッドは指定されたリストのスレッドセーフビューを返します:

List<Integer> syncList = Collections.synchronizedList(new ArrayList<>());

当然のことながら、 synchronizedList()メソッドの使用は、上位レベルの対応する synchronizedCollection()とほぼ同じように見えます。

したがって、前の単体テストで行ったように、同期された List を作成すると、複数のスレッドを生成できます。 その後、それらを使用して、スレッドセーフな方法でターゲットListにアクセス/操作します。

さらに、同期されたコレクションを反復処理して予期しない結果を防ぎたい場合は、ループの独自のスレッドセーフな実装を明示的に提供する必要があります。 したがって、synchronizedブロックを使用してこれを実現できます。

List<String> syncCollection = Collections.synchronizedList(Arrays.asList("a", "b", "c"));
List<String> uppercasedCollection = new ArrayList<>();
    
Runnable listOperations = () -> {
    synchronized (syncCollection) {
        syncCollection.forEach((e) -> {
            uppercasedCollection.add(e.toUpperCase());
        });
    }
};

同期されたコレクションを反復処理する必要があるすべての場合に、このイディオムを実装する必要があります。 これは、同期されたコレクションの反復が、コレクションへの複数の呼び出しを通じて実行されるためです。 したがって、これらは単一のアトミック操作として実行する必要があります。

同期ブロックを使用すると、操作の原子性が保証されます

4. synchronizedMap()メソッド

Collections クラスは、 synchronizedMap()と呼ばれる別のきちんとした同期ラッパーを実装します。同期されたMapを簡単に作成するために使用できます。

このメソッドは、提供されたMap実装のスレッドセーフビューを返します

Map<Integer, String> syncMap = Collections.synchronizedMap(new HashMap<>());

5. synchronizedSortedMap()メソッド

synchronizedMap()メソッドの対応する実装もあります。 これはsynchronizedSortedMap()と呼ばれ、同期されたSortedMapインスタンスを作成するために使用できます。

Map<Integer, String> syncSortedMap = Collections.synchronizedSortedMap(new TreeMap<>());

6. synchronizedSet()メソッド

次に、このレビューに進むと、 synchronizedSet()メソッドがあります。 その名前が示すように、最小限の手間で同期されたセットを作成できます。

ラッパーは、指定されたSetに基づくスレッドセーフコレクションを返します。

Set<Integer> syncSet = Collections.synchronizedSet(new HashSet<>());

7. synchronizedSortedSet()メソッド

最後に、ここで紹介する最後の同期ラッパーは、 synchronizedSortedSet()です。

これまでに確認した他のラッパー実装と同様に、メソッドは指定されたSortedSetのスレッドセーフバージョンを返します。

SortedSet<Integer> syncSortedSet = Collections.synchronizedSortedSet(new TreeSet<>());

8. 同期コレクションと同時コレクション

ここまで、コレクションフレームワークの同期ラッパーを詳しく見てきました。

ここで、ConcurrentHashMapBlockingQueueの実装など、同期コレクションと同時コレクションの違いに焦点を当てましょう。

8.1. 同期されたコレクション

同期されたコレクションは、組み込みロックによってスレッドセーフを実現し、コレクション全体がロックされます。 組み込みロックは、ラップされたコレクションのメソッド内の同期ブロックを介して実装されます。

ご想像のとおり、同期されたコレクションは、マルチスレッド環境でのデータの整合性/整合性を保証します。 ただし、一度に1つのスレッドしかコレクションにアクセスできないため、パフォーマンスが低下する可能性があります(別名 同期アクセス)。

同期メソッドとブロックの使用方法の詳細なガイドについては、トピックに関する記事を確認してください。

8.2. コンカレントコレクション

同時コレクション(例: ConcurrentHashMap)、データをセグメントに分割することでスレッドセーフを実現します 。 たとえば、 ConcurrentHashMap では、異なるスレッドが各セグメントのロックを取得できるため、複数のスレッドが同時に Map にアクセスできます(別名 同時アクセス)。

同時スレッドアクセスの固有の利点により、同時コレクションは同期コレクションよりもはるかにパフォーマンスが高くなります。

したがって、使用するスレッドセーフコレクションのタイプの選択は、各ユースケースの要件に依存し、それに応じて評価する必要があります。

9. 結論

この記事では、Collectionsクラス内に実装されている一連の同期ラッパーについて詳しく見てきました。

さらに、同期コレクションと同時コレクションの違いを強調し、スレッドセーフを実現するためにそれらが実装するアプローチについても説明しました。

いつものように、この記事に示されているすべてのコードサンプルは、GitHubから入手できます。