1

与えられた:

  • volatile遅延初期化シングルトン クラスはsynchronizedgetInstance. このシングルトンは、ExecutorService,を介して非同期操作を開始します
  • タスクには 7 つのタイプがあり、それぞれが一意のキーで識別されます。
  • タスクが起動されると、に基づいてキャッシュに保存されますConcurrentHashMap
  • クライアントがタスクを要求すると、キャッシュ内のタスクが完了すると、新しいタスクが起動されてキャッシュされます。実行中の場合、タスクはキャッシュから取得され、クライアントに渡されます。

コードの抜粋を次に示します。

private static volatile TaskLauncher instance;
private ExecutorService threadPool;
private ConcurrentHashMap<String, Future<Object>> tasksCache;

private TaskLauncher() {
    threadPool = Executors.newFixedThreadPool(7);
    tasksCache = new ConcurrentHashMap<String, Future<Object>>();
}

public static TaskLauncher getInstance() {
    if (instance == null) {
        synchronized (TaskLauncher.class) {
            if (instance == null) {
                instance = TaskLauncher();
            }
        }
    }
    return instance;
}

public Future<Object> getTask(String key) {
    Future<Object> expectedTask = tasksCache.get(key);
    if (expectedTask == null || expectedTask.isDone()) {
        synchronized (tasksCache) {
            if (expectedTask == null || expectedTask.isDone()) {
                // Make some stuff to create a new task
                expectedTask = [...];
                threadPool.execute(expectedTask);
                taskCache.put(key, expectedTask);
            }
        }
    }
    return expectedTask;
}

1 つの大きな質問と、もう 1 つの小さな質問があります。

  1. getTaskメソッドでロック制御をダブルチェックする必要がありますか? 私ConcurrentHashMapは読み取り操作がスレッドセーフであることを知っているので、get(key)スレッドセーフであり、ロックのダブルチェックは必要ないかもしれません (しかし、これについてはまだよくわかりません…)。しかしisDone()、Future のメソッドはどうでしょうか。
  2. synchronizedブロック内の適切なロック オブジェクトをどのように選択しますか? であってはならないことがわかっているnullので、最初に のTaskLauncher.classオブジェクトを使用しgetInstance()、次にメソッドtasksCacheで既に初期化されているを使用しますgetTask(String key)。そして、この選択は実際に重要ですか?
4

2 に答える 2

2

getTask メソッドでロック制御をダブルチェックする必要がありますか?

ここでは、ダブルチェック ロック (DCL) を行う必要はありません。(実際、DCL を使用する必要があることは非常にまれです。99.9% のケースでは、通常のロックで問題ありません。最新の JVM での通常のロックは十分に高速であるため、通常、DCL のパフォーマンス上の利点は小さすぎて目立ちません。違い。)

ただし、 であると宣言しない限り、同期必要です。そうでない場合は、単純なロックで問題ありません。tasksCachefinaltasksCachefinal

ConcurrentHashMap が読み取り操作に対してスレッドセーフであることは知っています...

それは問題ではありません。問題は、参照の値を読み取って、異なるスレッドで作成および使用されたtaskCache場合に正しい値が得られるかどうかです。TaskLauncher変数から参照をフェッチする際のスレッド セーフは、参照されるオブジェクトのスレッド セーフによって何らかの形で影響を受けることはありません。

しかし、Future の isDone() メソッドはどうでしょうか?

繰り返しますが、これは、DCL やその他の同期を使用する必要があるかどうかには関係ありません。

レコードについては、メモリ セマンティクスの「コントラクト」Futureが javadoc で指定されています。

「メモリの一貫性の影響: 非同期計算によって実行されるアクションは、別のスレッドで対応する Future.get() に続くアクションの前に発生します。」

get()つまり、 (適切に実装された)を呼び出すときに、追加の同期は必要ありませんFuture

同期ブロックで正しいロック オブジェクトを選択するにはどうすればよいですか?

ロックは、ロックを保持している間、異なるスレッドによって読み書きされる変数へのアクセスを同期するのに役立ちます。

理論的には、1 つのロックのみを使用するようにアプリケーション全体を作成できます。しかし、そうすると、最初のスレッドが別のスレッドによって使用された変数を使用する必要がないにもかかわらず、あるスレッドが別のスレッドを待機する状況が発生します。そのため、通常は変数に関連付けられたロックを使用します。

もう 1 つ確認しなければならないことは、2 つのスレッドが同じ変数セットにアクセスする必要がある場合、同じオブジェクト (またはオブジェクト) をロックとして使用することです。異なるロックを使用すると、適切な同期が実現されません...

(ロックオンかプライベートロックか、およびロックを取得する順序についても問題がありますがthis、これらはあなたが尋ねた質問の範囲を超えています。)

これらは一般的な「ルール」です。特定のケースで決定するには、何を保護しようとしているのかを正確に理解し、それに応じてロックを選択する必要があります。

于 2014-02-14T12:12:17.083 に答える
1
  1. サイドFutureTaskで使用されるAbstractQueuedSyncには、スレッドの可変状態と、揮発性 (スレッド セーフ) 変数があります。したがって、 isDone() メソッドについて心配する必要はありません。

    プライベートな volatile int 状態。

  2. ロック オブジェクトの選択は、インスタンスのタイプと状況に基づいています。複数のオブジェクトがあり、TaskLauncher.class に Sync ブロックがあり、すべてのインスタンスのすべてのメソッドがこの単一のロックによって同期されているとします (必要に応じてこれを使用します)。すべてのインスタンスで単一の共有メモリを共有します)。

すべてのインスタンスが独自の共有メモリを持つ場合、b/w スレッドとメソッドはthisを使用します。これを使用すると、余分なロック オブジェクトも 1 つ節約できます。あなたの場合、TaskLauncher.class 、tasksCache を使用できます。これは、同期に関してはすべてシングルトンと同じです。

于 2014-02-14T09:47:03.407 に答える