12

我有以下结构:

public void someMethod(){  
   //DO SOME STUFF
   try{  
    doSomeProcessing();  
   }  
   catch (Exception e){  
        loadSomeHeavyData();  
        doSomeProcessing();      
   }    
}  

该方法someMethod 可以被多个线程同时调用。doSomeProcessing 可能会抛出异常(它在后端使用了一些可能会过时的数据)。
如果抛出异常,则loadSomeHeavyData();执行一些耗时的任务,比如说“更新”所有当前数据,我可以调用doSomeProcessing();.
问题:如何确保loadSomeHeavyData();只调用一次?如果我在条目中放了一些原子标志,loadSomeHeavyData();那么我不能确定什么时候应该清除它。
我该如何解决这个问题?请注意:我无法修改doSomeProcessing();,因为它是一个外部 API,我正在使用装饰器模式来使用它。

4

5 回答 5

13

您的loadSomeHeavyData方法可以使用阻塞机制让所有线程等待它完成更新,但只让其中一个线程实际执行更新:

private final AtomicBoolean updateStarted = new AtomicBoolean();
private final CountDownLatch updateFinished = new CountDownLatch(1);

public void loadSomeHeavyData() {
    if (updateStarted.compareAndSet(false, true)) {
        //do the loading
        updateFinished.countDown();
    } else {
        //update already running, wait
        updateFinished.await();
    }
}

注意我的假设:

  • 您希望所有线程等到加载完成,以便他们可以doSomeProcessing使用更新的数据再次调用
  • 你只调用loadSomeHeavyData一次,如果没有,你将需要重置标志和 CountdownLatch (这可能不是最合适的机制)。

编辑

您的最新评论表明您实际上想要多次调用loadSomeHeavyData,但一次不超过一次

private final Semaphore updatePermit = new Semaphore(1);

public void loadSomeHeavyData() {
    if (updatePermit.tryAcquire()) {
        //do the loading and release updatePermit when done
        updatePermit.release();
    } else {
        //update already running, wait
        updatePermit.acquire();
        //release the permit immediately
        updatePermit.release();
    }
}
于 2012-11-13T07:18:56.157 回答
4

使用synchronized关键字:

public synchronized void someMethod(){  
    //doStuff
}

您保证一次只有一个线程进入。

确保该方法只被调用一次,没有特殊的语言特性;您可以创建一个布尔类型的静态变量,该变量由进入该方法的第一个线程设置为 true。调用该方法时,请始终检查该标志:

public class MyClass {
    private static boolean calledMyMethod;

    public synchronized void someMethod() {
        if(calledMyMethod) { 
            return;
        } else {
            calledMyMethod = true;
            //method logic
        }           
    }
} 
于 2012-11-13T07:02:49.023 回答
1
public void someMethod()
{  
    //DO SOME STUFF
    try
    {  
        doSomeProcessing();  
    }   
    catch (Exception e)
    {   
        loadSomeHeavyData();  // Don't call here but add a request to call in a queue.
                              // OR update a counter
        doSomeProcessing();      
    }
}

一种解决方案可能是创建一个队列,每个线程将其请求放入其中以调用loadSomeHeavyData。当没有。的请求达到阈值,阻止执行someMethod并调用loadSomeHeavyData并清除队列。

伪代码可能如下所示:

int noOfrequests = 0;
public void someMethod()
{
    // block incoming threads here.  
    while(isRefreshHappening);

    //DO SOME STUFF
    try
    { 
        doSomeProcessing();  
    }   
    catch (Exception e)
    {
        // Log the exception
        noOfrequests++;   
    }
}

// Will be run by only one thread
public void RefreshData()
{
    if(noOfrequests >= THRESHOLD)
    {
        isRefreshHappening = true;
        // WAIT if any thread is present in try block of somemethod
        // ...
        loadSomeHeavyData();
        noOfrequests = 0;
        isRefreshHappening = false;
    }
}
于 2012-11-13T07:27:18.193 回答
0

据我了解您的问题,您需要在不可预测但有限的时间间隔内加载数据。有三种可能的方法来做到这一点: 1) 你可以在调用 loadSomeHeavyData 的时候加上一个 if 语句来控制对该方法的访问。2)您可以更改处理控制流的方法(决定是否更新) 3)编写更新线程并让它为您完成工作 前两种选择可以使用外部布尔值或通过使用生成布尔决策最后一次调用和当前调用时间之间的时间差。第三种选择是一个定时线程,它每 n 秒/分钟运行一次并加载大量数据。

于 2012-11-13T07:21:10.190 回答
0

我们编写了一个库,其中包含一个延迟加载/调用方法的实用程序。它保证单次使用语义并按照您的预期保留任何抛出的异常。

用法很简单:

LazyReference<Thing> heavyThing = new LazyReference<Thing>() {
  protected Thing create() {
    return loadSomeHeavyData();
  }
};

public void someMethod(){  
  //DO SOME STUFF
  try{  
    doSomeProcessing();  
  }  
  catch (Exception e){  
    heavyThing.get();  
    doSomeProcessing();      
  }    
}  

所有线程都阻塞get()并等待生产者线程(第一个调用者)完成。

于 2012-11-13T07:31:32.023 回答