上下文
我有一个叫做ImageLoader
的类。
当我调用ImageLoader的getPicture( pictureID )
时,如果未缓存图片,则将pictureID
存储在一个实例变量中,创建一个新线程,该线程最终在同一callback()
类中调用ImageLoader
函数。然后callback()
使用此pictureID
作为缓存图片的键。
问题
你看到泡菜了吗?
如果我连续两次调用getPicture( somePictureID )
,而第一次调用的callback()
函数尚未发生,我将覆盖pictureID
函数将使用的先前的callback()
。 (如果您仍然看不到泡菜,请参见下面的代码。)
现在我知道您在想,为什么不只在变量上添加一点同步以使其成为线程安全的呢?
因为谁知道查询图片的线程将花费多长时间,这确实会减慢加载多张图片的整个过程。
我如何解决它的想法
因此,我的绝妙主意是创建一个内部类,该内部类将服务器作为从属服务器,并且只能使用一次。我只在该从属类中执行过一些功能,并且从不重用该对象。
问题
这是解决此类问题的合适方法吗?我觉得这可能会陷入我不知道的某种模式。如果我对此一无所知,您能建议另一种解决方案吗?
简化代码
//the instance variable with a race condition
private int pictureID
//loads the image associated with the pictureID
public void getPicture( int pictureID )
{
Bitmap bitmap = cache.get( pictureID );
if ( bitmap != null )
{
//load image
return;
}
int post_params[] = { pictureID, /* more parameters */ };
this.pictureID = pictureID; //PROBLEM: race condition!
new HttpRequest( this ).execute( post_params ); //starts new thread and queries server
}
//gets called when the query finishes, json contains my image
public void callback( JSONObject json )
{
Bitmap bitmap = getBitmap( json ); //made up method to simplify code
cache.put( this.pictureID, bitmap ); //PROBLEM: race condition!
//load image
}
最佳答案
对我来说,这看起来像是Memoizer模式的经典案例,最简单的使用方法可能是Guava的CacheBuilder,例如:
private LoadingCache<Integer, Image> cache = CacheBuilder.newBuilder().
build(new CacheLoader<Integer, Image>() {
@Override
public Image load(Integer key) throws Exception {
return httpGetImageExpensively(key);
}
});
public Image getPicture(int pictureId) {
return cache.getUnchecked(pictureId); // blocks until image is in cache
}
现在,HTTP请求将在第一个调用给定ID的
getPicture()
的线程中发生;具有该ID的后续调用者将阻塞,直到第一个线程将图像放入缓存中为止,但您可以同时请求多个ID。要考虑的事情:
cache.get()
方法抛出一个(已检查的)ExecutionException
,这就是为什么我改用getUnchecked()
的原因,但是它只是抛出了一个(未经检查的)UncheckedExecutionException
。如果可能的话,我会在httpGetImageExpensively()
中进行大多数错误处理,并仔细考虑那里的可能情况(错误的ID,DNS故障等),但是getPicture()
的调用者仍需要以某种方式处理该错误。 getPicture()
,然后在内部将对httpGetImageExpensively()
的实际调用放在ExecutorService
上,以便getPicture()
可以立即返回。 (取决于应用程序,可能还有其他问题,例如在Swing应用程序中,可能希望在事件分配线程上调用回调。)