Java 同步一个特定的Web服务调用
我想知道解决这个问题的最好办法是什么。我有一个(抽象地说)简单的方法,它调用Web服务并将结果存储在本地内存缓存中,类似于:Java 同步一个特定的Web服务调用,java,multithreading,webservice-client,Java,Multithreading,Webservice Client,我想知道解决这个问题的最好办法是什么。我有一个(抽象地说)简单的方法,它调用Web服务并将结果存储在本地内存缓存中,类似于: public Document callWebservice(SomeObject parameter) { Document result = cache.get(parameter); if (result == null) { result = parse(retrieve(parameter)); cache.pu
public Document callWebservice(SomeObject parameter) {
Document result = cache.get(parameter);
if (result == null) {
result = parse(retrieve(parameter));
cache.put(result);
}
return result;
}
现在,如果文档在缓存中,它可以毫无问题地返回,很好。在单线程环境中,这种方法也可以很好地工作。但是,在多线程环境中,每个线程都会转到“else”位,并多次调用Web服务
我可以在“else”部分抛出一个同步块,但我认为这锁太“宽”——整个方法将无法用于调用线程,即使它们调用完全不同的东西
只要请求不同(在本例中是SomeObject参数),就可以调用两次webservice
现在,问题:在这种情况下,最好的方法是什么
我考虑将参数存储在(线程安全)集合对象中。如果参数的内容相同,它将生成相同的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;
}
}
私有映射currentlyProcessingItems=new ConcurrentHashMap();
公共文档callWebservice(SomeObject参数){
if(currentlyProcessedItems.contains(参数)){
参数=currentlyProcessedItems.get(参数);
}否则{
currentlyProcessedItems.putIfAbsent(参数);
}
已同步(参数){
文档结果=cache.get(参数);
如果(结果==null){
文档结果=解析(检索(参数));
cache.put(结果);
}
currentlyProcessedItems.remove(参数);
返回结果;
}
}
(注意:跟踪当前正在处理的请求、ConcurrentHashMap的使用和锁定的逻辑可能不理想或完全错误)
不,我从来没有真正读完过关于线程的书。我应该
我很确定这个问题很普遍,我就是找不到答案。如果我可以问的话,这种情况叫做什么(即锁定特定对象)?Java 5及以上版本有一些类可以帮助实现这一点。您可能会发现ConcurrentHashMap很有趣:我自己也在考虑这个问题,尤其是当检索(参数)需要很长时间时(对我来说,是连接到服务器以检查身份验证、处理/过滤后端请求等)。现在我自己还没有尝试过,但是为了讨论,这听起来怎么样 要使用缓存,必须从线程调用new MyCache(key).getValue(),因为getValue将被锁定在getCacheValue()方法中,直到该值(对所有等待的线程)可用为止
警告,xml DOM对象不是线程安全的。因此(忽略缓存本身的线程安全问题),您不能在多个线程之间共享文档实例。我们在自己的代码库中对此有所了解,所以我知道这是有问题的(不仅仅是一些理论问题)
至于缓存本身,我相信guava具有您想要的功能:我已经尝试了我自己的想法,它实际上似乎工作得很好-首先,它使用了一个
ConcurrentHashMap
(虽然一个好的并发集会更好,但还没有找到一个合适的实现)维护当前正在处理的对象的列表
在方法本身中,它查找映射是否已经包含标识符的实例。如果没有,它将尝试使用putIfAbsent()
插入标识符。如果在检查和插入之间有另一个线程插入了它,那么putIfAbsent方法将返回另一个线程的插入项并使用该项
然后,使用新标识符或现有标识符,启动一个同步块,锁定标识符实例。在synchronized块中,将查询缓存,如果不返回结果,则调用webservice。实际上,这意味着执行相同请求(由标识符标识)的所有进程只能同时处理其中一个请求,因此当第一个进程调用webservice时,其他进程将排队等待。轮到它们时,结果在缓存中,等待的线程可以从那里检索它
至于文档对象不是线程安全的,我还不确定该怎么办。在我本地的机器上,它似乎不会引起任何问题,但像这样的事情有一个随机出现在生产中的习惯。Grr。我想,你必须找出这个答案。你在某个对象上正确地使用了equals和hashCode?是的,该参数有一个很好的
hasCode()
和equals()
方法,基于Commons Lang的HashCodeBuilder和EqualsBuilder。在内部,它也有一些对象,它们也正确地实现了警告中的hashCode()
和equals()
+1,还有一个+1用于将我指向ComputingMap,它看起来就像我要寻找的东西。
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
}}}