0

LruCache がすべてのスレッドに最新情報を提供すること、および同じスレッドが別のスレッドからのキャッシュの編集を見る前に 1 つのスレッドの操作が完了することを Android が保証していることをどこかで読んだことを思い出します。LruCache を使用してアプリのサーバーから取得したビットマップを格納し、スレッドのプールを使用してネットワークからビットマップを取得しています。

Androidのドキュメントやその他の言及でこれへの参照を見つけることができません。LruCache インスタンスを揮発性としてマークするか、キャッシュ操作に同期 (LruCache) を設定する必要がありますか?

4

1 に答える 1

2

Android LruCache Thread Safetyに関する mibollma の回答は間違っていません。スレッドの安全性とアトミック性をよく間違えます。

クラスがスレッド セーフである場合、たとえば 2 つのスレッドがそのクラスの操作を呼び出しても、内部が壊れないことを意味します。Vector は、すべての操作が同期されるクラスです。2 つの異なるスレッドが Vector.add を呼び出した場合、それらは両方ともインスタンスで同期し、状態は壊れません。たとえば、次のようなものです。

synchronized void add(final T obj) {
  objects[index++] = obj;
}

2 つのスレッドが同じ位置に要素を追加しないという意味で、これはスレッドセーフです。同期されない場合、両方とも index = 0 を読み取り、その位置に書き込もうとする可能性があります。

それでも同期する必要があるのはなぜですか?次のようなケースがあるとします。

if(!collection.contains(element)) {
  collection.add(element);
}

その場合、操作はアトミックではありません。要素が既に存在するかどうかを尋ねるときに 1 回同期し、その要素を追加するときに 2 回目の同期を行います。ただし、これら 2 つの呼び出しの間には、別のスレッドが進行する可能性があり、コレクションに要素が含まれていないという仮定が崩れる可能性があります。

擬似コード:

if(!coll.contains(element)) {         // << you have the exclusive lock here
  //Thread 2 calls coll.add(element)     << you do not have the lock anymore
  coll.add(element);                  // << doomed!
}

これが、次のような非アトミック操作を中心に同期する必要があるという意味で、答えが正しい理由です。

synchronized(coll) {
    if(!coll.contains(element)) {       // << you have the exclusive lock here
      // Thread 2 wants to call            << still holding the lock
      // coll.add(element) but
      // cannot because you hold the lock
      coll.add(element);                // << unicorns!
    }
}

同期はかなりコストがかかるため、同時コレクションには のようなアトミック操作が付属していputIfAbsentます。

元の質問に戻ります。LruCache を揮発性にする必要がありますか? 一般に、LruCache 自体を volatile とマークするのではなく、それへの参照をマークします。そのような参照がスレッド間で共有されていて、そのフィールドを更新する予定がある場合は、はい。

フィールドが volatile とマークされていない場合、スレッドは更新された値を認識しない可能性があります。繰り返しますが、これは LruCache 自体への単なる参照であり、その内容とは直接関係ありません。

nullあなたの特定のシナリオでは、とにかく参照を設定するスレッドがないため、参照を volatile ではなく final にしたいと思います。

キャッシュ操作を同期化する必要があるかどうかは、ケースによって異なります。のような単一のアトミック操作を作成する場合はputIfAbsent、はい。

public void putIfAbsent(final K key, final V value) {
  synchronized(lruCache) {
    if(!lruCache.containsKey(key)) {
      lruCache.put(key, value);
    }
  }
}

ただし、コードの後半で呼び出すときに、lruCache.get(key)それを同期ブロック自体にラップする必要はありません。別のスレッドに干渉しないアトミック操作を作成する場合のみ。

于 2013-04-15T13:48:21.553 に答える