如何确保方法只被多个线程调用一次?

我有以下结构:

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

someMethod方法可以被许multithreading同时调用。 doSomeProcessing 可能会抛出exception(它使用后端中的某些数据可能会过时)。
如果抛出exception,则loadSomeHeavyData(); 做一些耗时的任务,让我们说“更新”所有当前数据,我可以调用doSomeProcessing();
问题:如何确保loadSomeHeavyData(); 只被召唤一次 ? 如果我在loadSomeHeavyData();的条目中放入一些primefaces标志loadSomeHeavyData(); 然后我不能确定何时应该清除它。
我怎么解决这个问题? 只是注意:我无法修改doSomeProcessing(); 因为它是一个外部API,我使用装饰模式来使用它。

您的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(); } } 

使用synchronized关键字:

 public synchronized void someMethod(){ //doStuff } 

您确保一次只能输入一个线程。

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

 public class MyClass { private static boolean calledMyMethod; public synchronized void someMethod() { if(calledMyMethod) { return; } else { calledMyMethod = true; //method logic } } } 
 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 。 什么时候没有 请求到达threashold,阻止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; } } 

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

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

用法很简单:

 LazyReference heavyThing = new LazyReference() { protected Thing create() { return loadSomeHeavyData(); } }; public void someMethod(){ //DO SOME STUFF try{ doSomeProcessing(); } catch (Exception e){ heavyThing.get(); doSomeProcessing(); } } 

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