3

可能であるべきかどうかわからないことが起こりました。私はそれを見たので、明らかにそうですが、根本的な原因を見つける必要があり、皆さんが助けてくれることを望んでいました.

郵便番号の緯度と経度を検索するシステムがあります。毎回アクセスするのではなく、安価なインメモリ HashTable キャッシュに結果をキャッシュします。これは、郵便番号の緯度と経度がリリースよりも頻繁に変更されない傾向があるためです。

とにかく、ハッシュは、両方とも同期されている「get」メソッドと「add」メソッドを持つクラスに囲まれています。このクラスにはシングルトンとしてアクセスします。

これが最良のセットアップだと主張しているわけではありませんが、それが私たちの目標です。(できるだけ早く Collections.synchronizedMap() 呼び出しで Map をラップするように変更する予定です。)

このキャッシュをマルチスレッド環境で使用し、2 つの zip に対して 2 つの呼び出しをスレッド化します (2 つの間の距離を計算できるようにするため)。これらはほぼ同時に発生する場合があるため、両方の呼び出しが同時にマップにアクセスする可能性が非常に高くなります。

つい最近、2 つの異なる郵便番号が同じ値を返すというインシデントが発生しました。初期値が実際に異なっていたと仮定すると、Map に値を書き込むと、2 つの異なるキーに対して同じ値が書き込まれる可能性はありますか? または、2 つの「取得」がワイヤを交差させて誤って同じ値を返す可能性がある方法はありますか?

私が持っている他の唯一の説明は、初期データが破損していた (間違った値) ということですが、それはほとんどありそうにありません。

どんなアイデアでも大歓迎です。ありがとう、ピーター

(PS: 詳細情報やコードなどが必要な場合はお知らせください)

public class InMemoryGeocodingCache implements GeocodingCache
{

private Map cache = new HashMap();
private static GeocodingCache instance = new InMemoryGeocodingCache();

public static GeocodingCache getInstance()
{
    return instance;
}

public synchronized LatLongPair get(String zip)
{
    return (LatLongPair) cache.get(zip);
}

public synchronized boolean has(String zip)
{
    return cache.containsKey(zip);
}

public synchronized void add(String zip, double lat, double lon)
{
    cache.put(zip, new LatLongPair(lat, lon));
}
}


public class LatLongPair {
double lat;
double lon;

LatLongPair(double lat, double lon)
{
    this.lat = lat;
    this.lon = lon;
}

public double getLatitude()
{
    return this.lat;
}

public double getLongitude()
{
    return this.lon;
}
}
4

8 に答える 8

8

コードは正しいようです。

唯一の懸念は、lat と lon がパッケージから見えることです。そのため、同じパッケージ コードに対して次のことが可能です。

LatLongPair llp = InMemoryGeocodingCache.getInstance().get(ZIP1);
llp.lat = x;
llp.lon = y;

これにより、明らかにキャッシュ内オブジェクトが変更されます。

したがって、緯度と経度も最終的なものにします。

PS キー (郵便番号) は一意で小さいため、すべての操作でハッシュを計算する必要はありません。TreeMap を使用する方が簡単です (Collections.synchronizedMap() にラップされています)。

PPS 実用的なアプローチ: 終わりのないループで put/get 操作を行う 2 つのスレッドのテストを作成し、get ごとに結果を検証します。ただし、そのためにはマルチCPUマシンが必要です。

于 2008-10-28T20:48:56.823 に答える
6

なぜそれが起こっているのかを知るのは難しいです。より多くのコードが役立つ可能性があります。

とにかく ConcurrentHashMap を使用する必要があります。これは、一般に、同期されたマップよりも効率的です。アクセスを同期するのではなく、内部で処理します (より効率的に)。

于 2008-10-28T18:39:59.970 に答える
4

注意すべきことの 1 つは、キーまたは値が変更されている可能性があるかどうかです。たとえば、挿入ごとに新しいオブジェクトを作成するのではなく、既存のオブジェクトの値を変更して再挿入する場合です。

また、キー オブジェクトが hashCode と equals の両方を定義して、HashMap 契約に違反しないようにする必要があります (つまり、equals が true を返す場合、hashCodes は同じである必要がありますが、必ずしもその逆である必要はありません)。

于 2008-10-28T18:47:13.137 に答える
3

LatLonPair が変更されている可能性はありますか? コードの他の場所で誤って変更されないように、緯​​度と経度のフィールドを最終的なものにすることをお勧めします。

シングルトンの「インスタンス」とマップ参照の「キャッシュ」も最終的なものにする必要があることに注意してください。

于 2008-10-28T20:10:51.863 に答える
2

ジェームズは正しいです。オブジェクトを返すため、その内部が変更される可能性があり、そのオブジェクト (マップ) への参照を保持するものはすべてその変更を反映します。最終は良い答えです。

于 2008-10-28T20:52:52.200 に答える
0

あなたが投稿したコードに、あなたが説明した問題を引き起こすような問題は何もありません。私の推測では、問題があるのは地理コードキャッシュのクライアントの問題だと思います。

考慮すべき他の事柄(これらのいくつかはかなり明白ですが、とにかくそれらを指摘すると思いました):

  1. 問題が発生した2つの郵便番号はどれですか。ソースシステムに同一の地理コードがないことを確認しますか?
  2. 2つの同じ郵便番号を誤って比較していないことを確認しますか?
于 2008-10-29T02:20:59.587 に答える
0

has(String ZIP)メソッドが存在するということは、コードに次のようなものがあることを意味します。

GeocodingCache cache = InMemoryGeocodingCache.getInstance();

if (!cache.has(ZIP)) {
    cache.add(ZIP, x, y);
}

残念ながら、これにより、falseを返すhas()とadd()の追加の間で問題を同期することができ、説明した問題が発生する可能性があります。

より良い解決策は、チェックをaddメソッド内に移動して、チェックと更新が次のような同じロックでカバーされるようにすることです。

public synchronized void add(String zip, double lat, double lon) {
    if (cache.containsKey(zip)) return;
    cache.put(zip, new LatLongPair(lat, lon));
}

getInstance()をシングルトンとして使用している場合は、新しいInMemoryGeocodingCache()を使用して追加のキャッシュが作成される可能性を防ぐためのプライベートコンストラクターが必要です。

于 2008-11-06T11:38:54.837 に答える
0

HashMap の Java ドキュメントは次のとおりです。

http://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html

この実装は同期されていないことに注意してください。複数のスレッドが同時にハッシュ マップにアクセスし、少なくとも 1 つのスレッドがマップを構造的に変更する場合は、外部で同期する必要があります。(構造変更とは、1 つ以上のマッピングを追加または削除する操作です。インスタンスに既に含まれているキーに関連付けられた値を単に変更するだけでは、構造変更ではありません。)これは通常、マップを自然にカプセル化するオブジェクトを同期することによって達成されます。 . そのようなオブジェクトが存在しない場合は、Collections.synchronizedMap メソッドを使用してマップを「ラップ」する必要があります。これは、マップへの偶発的な非同期アクセスを防ぐために、作成時に行うのが最適です。

Map m = Collections.synchronizedMap(new HashMap(...));

または、 java.util.concurrent.ConcurrentHashMap を使用してください

于 2013-11-14T16:34:25.727 に答える