Java 同步一个特定的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

我想知道解决这个问题的最好办法是什么。我有一个(抽象地说)简单的方法,它调用Web服务并将结果存储在本地内存缓存中,类似于:

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
}}}