5

特定のユースケースを念頭に置いていましたが、使用する適切なデータ構造を理解できませんでした。

オブジェクトをHashMapにストリーミングし続けるスレッドが1つあります。ダニの頻度が高く、未知である市場データに似たもの。

更新されたPriceオブジェクトのこのマップを常に読み取り、キーごとに特定の順序でクエリを実行する別のスレッド。クエリは、特定のサイクルで同じキーに対して複数回行われる場合があります。読み取りと書き込みは非常に頻繁に行われますが、読み取りスレッドは完全に更新された最新の利用可能なデータにのみ関心があり、書き込みが完了するまで必ずしもブロックするわけではありません。

そのようなユースケースの理想的なデータ構造について、あなたの考えが欲しかったのです。利用可能なConcurrentHashMapよりも優れた実装はありますか?

ありがとう

4

3 に答える 3

2

ConcurrentHashMap。Javadocから

取得の完全な同時実行性と更新の調整可能な予想同時実行性をサポートするハッシュテーブル。このクラスは、Hashtableと同じ機能仕様に従い、Hashtableの各メソッドに対応するバージョンのメソッドが含まれています。ただし、すべての操作がスレッドセーフであっても、取得操作にはロックが必要ではなく、すべてのアクセスを妨げる方法でテーブル全体をロックすることはサポートされていません。このクラスは、スレッドセーフに依存しているが、同期の詳細には依存していないプログラムで、Hashtableと完全に相互運用できます。

通常、取得操作(getを含む)はブロックされないため、更新操作(putおよびremoveを含む)と重複する場合があります。取得は、開始時に保持されている最後に完了した更新操作の結果を反映します。putAllやclearなどの集計操作の場合、同時取得は一部のエントリのみの挿入または削除を反映する場合があります。同様に、イテレータと列挙は、イテレータ/列挙の作成時または作成以降のある時点でのハッシュテーブルの状態を反映する要素を返します。

于 2012-11-08T20:38:20.643 に答える
1

1つのアプローチは、次のようなコピーオンライトスキームです。

public class Prices {
    private volatile Map<String, Integer> prices = Collections.emptyMap();

    public void putPrice(String ticker, int price) {
        HashMap<String, Integer> newPrices = new HashMap<String, Integer>(prices);
        newPrices.put(ticker, price);
        prices = newPrices;
    }

    public Integer getPrice(String ticker) {
        return prices.get(ticker);
    }
}

これには、getのオーバーヘッドが最小限に抑えられます。1つは揮発性から読み取り、次に通常のハッシュルックアップです。ただし、プットにはかなりのオーバーヘッドがあります。まったく新しいマップの作成に加えて、揮発性ファイルへの書き込みです。読み取りと書き込みの比率が高い場合でも、これは適切なトレードオフになる可能性があります。

これを改善するには、既存のエントリを更新するのではなく、実際に新しいエントリを追加する必要があるときにのみマップを変更します。可変値を使用することでこれを実現できます。

public class Prices {
    private volatile Map<String, AtomicInteger> prices = Collections.emptyMap();

    public void putPrice(String ticker, int price) {
        AtomicInteger priceHolder = prices.get(ticker);
        if (priceHolder != null) {
            priceHolder.set(price);
        }
        else {
            HashMap<String, AtomicInteger> newPrices = new HashMap<String, AtomicInteger>(prices);
            newPrices.put(ticker, new AtomicInteger(price));
            prices = newPrices;
        }
    }

    public Integer getPrice(String ticker) {
        AtomicInteger priceHolder = prices.get(ticker);
        if (priceHolder != null) return priceHolder.get();
        else return null;
    }
}

のパフォーマンス特性が何であるかはわかりませんAtomicInteger。これは見た目よりも遅い可能性があります。不当に遅いと仮定するとAtomicInteger、これはかなり速いはずです-揮発性からの2回の読み取りと、取得ごとの通常のハッシュルックアップ、および揮発性からの読み取り、ハッシュルックアップ、および既存の更新のための揮発性への1回の書き込みが含まれます価格。それでも、新しい価格を追加するためにマップを複製する必要があります。ただし、一般的な市場では、それは頻繁には発生しません。

于 2012-11-08T22:42:56.570 に答える
1

データの更新中にマップが変更されない場合(つまり、プットまたは削除がない場合)、ConcurrentHashMapのような同期されたマップも必要ありません。プログラムの実行中に継続的にプットとリムーブがある場合は、これらの呼び出しを同期する必要があります。ただし、ConcurrentHashMapでさえ、更新頻度が高くなると(つまり、マルチスレッドプログラムで)、ConcurrentModificationExceptionsをスローし始めます。どの周波数が高すぎますか?あなたはそれを自分で測定しなければならないかもしれません、それはあなたのプラットフォームの多くの要因に依存します。

このような状況で私が行うことは、プログラムの実行中にマップを挿入または削除する必要がなく、データストリームが停止したときの起動時とシャットダウン時にのみ行う状況を作成することです。それが不可能な場合は、通常のHashMapと優れたデータ構造CopyOnWriteArrayListを組み合わせて使用​​し、外部で同期します。ConcurrentHashMapの制限をテストしていませんが、自分の本番システムでは信頼できません。

編集:ConcurrentHashMapはConcurrentModificationExceptionsを引き起こしません。Collections.synchronizedMapを使用する場合にのみ、問題が発生する可能性があります。

于 2012-11-08T20:49:39.337 に答える