我想知道解决这个问题的最佳方法是什么。我有一个(抽象地说)简单方法,它调用web服务并将结果存储在本地内存缓存中,如:
public Document callWebservice(SomeObject parameter) {
Document result = cache.get(parameter);
if (result == null) {
result = parse(retrieve(parameter));
cache.put(result);
}
return result;
}
现在,如果Document在缓存中,它可以正常返回,没问题。在单线程环境中,这种方法也可以正常工作。但是,在多线程环境中,事实证明每个线程将转向'else'位,并多次调用webservice。
我可以在'else'部分中查找一个同步块,但是我认为它太“宽”了 - 即使它们调用完全不同的东西,整个方法也无法调用线程。
如果webservice被调用两次,只要请求不同(例如SomeObject参数,在这种情况下),就可以了。
现在,问题:在这种情况下采取的最佳方法是什么?
我想过将参数存储在(threadsafe)集合对象中。如果参数的内容相同,它将产生相同的hashCode / equals结果,并且将在集合对象中找到,表明另一个线程已在处理此请求。如果是这种情况,则可以暂停调用线程,直到webservice返回。 (我必须弄清楚如何使调用线程等待)。这是否可以锁定SomeObject
参数对象? e.g:
private Map<SomeObject, SomeObject> currentlyProcessingItems = new ConcurrentHashMap<SomeObject, SomeObject>();
public Document callWebservice(SomeObject parameter) {
if (currentlyProcessedItems.contains(parameter)) {
parameter = currentlyProcessedItems.get(parameter);
} else {
currentlyProcessedItems.putIfAbsent(parameter);
}
synchronized(parameter) {
Document result = cache.get(parameter);
if (result == null) {
Document result = parse(retrieve(parameter));
cache.put(result);
}
currentlyProcessedItems.remove(parameter);
return result;
}
}
(注意:跟踪当前处理请求的逻辑,ConcurrentHashMap的使用和锁定可能不是最理想的或完全错误的)
不,我从未真正读过有关线程的书。我应该。
我很确定这个特殊问题很常见,我只是无法找到答案。如果我可以问的话,这样的情况叫什么(即锁定特定对象)?
答案 0 :(得分:1)
警告,xml DOM对象不是线程安全的。所以(忽略缓存本身的线程安全问题)你不能在多个线程之间共享Document实例。我们在自己的代码库中得到了一点点,所以我知道这是一个有问题的事实(不仅仅是一些理论问题)。
至于缓存本身,我相信番石榴有你想要的东西:a computing map
答案 1 :(得分:0)
Java 5及更高版本的类有助于完成这一任务。您可能会发现ConcurrentHashMap很有趣:http://www.javamex.com/tutorials/synchronization_concurrency_8_hashmap.shtml
答案 2 :(得分:0)
我自己正在考虑这个问题,特别是当retrieve(参数)需要很长时间时(对我而言,它连接到服务器以检查身份验证,后端处理/过滤请求等)。现在我还没有尝试过这个,但为了讨论,这听起来怎么样?
要使用缓存,必须从线程调用新的MyCache(key).getValue(),因为getValue将在方法getCacheValue()中被锁定,直到此值可用(对所有等待的线程)。
public class MyCache {
private final static HashMap cacheMap = new HashMap(); // One Map for all
private final static Vector fetchList = new Vector(); // One List for all
private Object cacheValue;
private boolean waitingState;
public MyCache (Object key) {
if (cacheMap.containsKey (key)) { // somebody has done it
cacheValue = cacheMap.get (key);
} else {
waitingState = true;
if (fetchInProgress (key, false)) // someone else is doing it
return;
new Thread (new MyFetch (key)).start();
}}
synchronized private static boolean fetchInProgress (Object key, boolean remove) {
if (remove) {
fetchList.remove (key);
} else {
boolean fetchingNow = fetchList.contains (key);
if (fetchingNow)
return true;
fetchList.addElement (key);
}
return false;
}
public Object getValue () {
if (waitingState)
getCacheValue (true);
return cacheValue;
}
synchronized private void getCacheValue (boolean waitOnLock) {
if (waitOnLock) {
while (waitingState) {
try {
wait();
} catch (InterruptedException intex) {
}}} else {
waitingState = false;
notifyAll();
}}
public class MyFetch implements Runnable {
private Object fetchKey;
public MyFetch (Object key) {
fetchKey = key; // run() needs to know what to fetch
}
public void run () { // Grab the resource, handle exception, etc.
Object fetchedValue = null;
// so it compiles, need to replace by callWebService (fetchKey);
cacheMap.put (fetchKey, fetchedValue); // Save for future use
fetchInProgress (fetchKey, true); // Remove from list
getCacheValue (false); // Notify waiting threads
}}}
答案 3 :(得分:0)
我已经给出了自己的想法,它实际上似乎工作正常 - 首先,它使用ConcurrentHashMap
(虽然好的并发Set会更好,但尚未找到正确的实现)维护当前正在处理的对象列表。
在方法本身中,它会查找地图是否已包含标识符的实例。如果没有,则尝试使用putIfAbsent()
插入标识符。如果在check和insert之间插入另一个线程,则putIfAbsent方法返回另一个线程的插入项,并使用该项。
然后,使用新标识符或现有标识符,启动同步块,锁定标识符实例。在synchronized块中,查询缓存,如果不返回结果,则调用webservice。实际上,这意味着执行相同请求的所有进程(由标识符标识)只能同时处理其中一个请求,因此当第一个请求调用Web服务时,其他请求排队等待。当轮到他们时,结果就在缓存中,等待的线程可以从那里检索它。
至于Document对象不是线程安全的,我不知道该怎么办呢。在我的本地机器上它似乎没有引起任何问题,但是像这样的东西有随意出现在生产中的习惯。格儿。我想这将不得不计算出来。