我应该将LruCache标记为易变?

时间:2013-04-15 04:23:24

标签: android multithreading caching

我记得在某处读取android确保LruCache为所有线程提供最新信息,并且一个线程的操作将在同一线程看到来自另一个线程的缓存编辑之前完成。我正在使用LruCache来存储从我的应用服务器获得的位图,并使用线程池从网络中获取位图。

现在我无法在Android文档或任何其他提及中找到对此的引用。我是否需要将LruCache实例标记为volatile或围绕缓存操作设置同步(LruCache)?

1 个答案:

答案 0 :(得分:2)

mibollma对Android LruCache Thread Safety的答复没有错。人们常常误认为线程安全和原子性。

如果一个类是线程安全的,那就意味着,当例如两个线程调用一个操作时,内部不会中断。 Vector就是这样一个类,每个操作都是同步的。如果两个不同的线程调用Vector.add,它们将在实例上同步并且状态不会中断。比如像这样:

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

这个线程安全的意思是没有两个线程会在同一个位置添加一个元素。如果它不同步,它们都可以读取index = 0并尝试在该位置写入。

现在为什么还需要同步?想象一下你有这样的案例:

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

在这种情况下,您的操作不是原子的。当您询问元素是否已存在时以及第二次添加该元素时,可以同步一次。但是当另一个线程可以取得进展并且你对不包含该元素的集合的假设被破坏时,在这两个调用之间有一个窗口。

在伪代码中:

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本身的引用,与其内容无直接关系。

在您的特定场景中,我宁愿使用引用final而不是volatile,因为任何线程都不应该将引用设置为null

是否需要在缓存操作周围进行同步的问题取决于具体情况。如果要创建单个原子操作,如putIfAbsent,则为是。

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

但是稍后在你的代码中,当你只调用lruCache.get(key)时,不需要将它包装成同步块本身。仅当您计划创建不应干扰另一个线程的原子操作时。