【发布时间】:2018-06-01 13:21:43
【问题描述】:
我有两个线程和一个缓存。我们将线程 1 称为 Tb,它是一个后台线程(即具有较低优先级),另一个线程称为 Tm(具有较高优先级的主线程)。有一个由两个线程更新的缓存。你可以说线程 Tb 是 Tm 的一个助手,用于在可能的情况下抢先填充缓存。
问题是当 Tm 想要访问缓存时,它必须立即访问它,因为某些 UI 更新显示依赖于它。
由于缓存是共享并发写入的,所以我对缓存的同步访问如下:
Element checkAndUpdateCache(int elementPositionToBeChecked){
Element toBeReturned;
synchronized(lock){
// Check if the element is already present in the cache
if(!cache.hasElement(elementPositionToBeChecked)){
// If not, retrieve a new one and fill the cache
toBeReturned = retrieveNewElement(elementPositionToBeChecked);
cache.put(elementPositionToBeChecked, toBeReturned );
}
else{
toBeReturned = cache.getElement(elementPositionToBeChecked);
}
}
return toBeReturned;
}
问题在于,由于后台线程在循环中调用此方法,它需要非常快速地继续访问缓存并且几乎从不释放锁。目前,我在每个循环周期后调用 Thread.yield() 并另外调用 Thread.sleep(10) 以提供对主线程的一些访问权限。
拥有不同的优先级并没有真正的帮助,在每个循环周期调用 Thread.yield() 也无济于事。 Thread.sleep() 确实有所帮助,但我认为,我们都同意,这根本不是一个好策略。毕竟,我们想要最大的 CPU 利用率,对吧?
有什么方法可以确保无论何时主线程需要访问缓存,它都能在后台线程等待它并稍后恢复操作时轻松获取它?
编辑:实施细节
缓存是Map<Integer, Album>,其中键是Integer。
public static Album getAlbum(Context context, int position, @NonNull Cursor cursor, @NonNull Map<Integer, Album> cache){
// Do we have the Album in cache
Album albumInfo = cache.get(position);
if(albumInfo == null){
cursor.moveToPosition(position);
// Let's cache this Album
albumInfo = Album.fromMediaStoreCursor(context, cursor);
cache.put(position, albumInfo);
}
EDIT 2
return albumInfo;
}
EDIT 2:正在节流的后台线程循环
// While we pre-emptively fetch the Albums to cache in the background :)
if (cursorImages != null) {
for (int i = 0; i < cursorImages.getCount(); i++) {
synchronized(SnapsboardApplication.getInstance()) {
AlbumsListCursorAdapter.getAlbum(ListPhotoVideoAlbumsOnDeviceActivity.this,
i, cursorImages, cache);
}
// Keep checking if we have been asked to cancel
if (isCancelled()) {
return null;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread.yield();
}
}
【问题讨论】:
-
缓存是用什么实现的?如果您使用
java.util.concurrent集合,则根本不需要同步。 -
Tm是向缓存本身添加数据还是纯粹的读取访问? -
@EJP :需要同步,因为如果一个线程发现 pos p 的缓存为空,并且在填充缓存时,主线程检查 pos p 的缓存并发现它是空的,但它是快满了
-
@Lothar Tm 也仅使用此方法将数据添加到缓存中。我的意思是,任何一个线程都必须第一次检索不存在于某个位置的数据。后台线程只是尽力提供帮助
-
使用
ConcurrentHashMap.computeIfAbsent()可将其减少到几行,无需手动同步并提高吞吐量,因为整个缓存不再锁定。 Guava 的Cache也不错。
标签: java multithreading