我怎样才能确保一个方法仅由多个线程调用一次?(How can I make sure a metho

2019-07-03 18:30发布

我有以下结构:

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

该方法someMethod 可以同时由多个线程调用。 该doSomeProcessing 可能会引发异常(它在后台,可能成为过时的使用一些数据)。
如果一个异常被抛出然后loadSomeHeavyData(); 做一些耗时的任务,让我们说“更新”目前所有的数据,我能够调用doSomeProcessing();
问题:我如何才能确保loadSomeHeavyData(); 只调用一次 ? 如果我把一些原子标志的进入loadSomeHeavyData(); 然后,我不能肯定这时候应该清除。
我该如何解决这个问题? 只是注意:我不能修改doSomeProcessing(); 因为它是一个外部API,我使用Decorator模式来使用它。

Answer 1:

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


Answer 2:

使用synchronized关键字:

public synchronized void someMethod(){  
    //doStuff
}

你保证只有一个线程在同一时间进入。

为了保证该方法只调用一次,没有任何特殊的语言特征; 你可以创建一个由输入法的第一个线程设置为true boolean类型的静态变量。 当调用方法总是检查标志:

public class MyClass {
    private static boolean calledMyMethod;

    public synchronized void someMethod() {
        if(calledMyMethod) { 
            return;
        } else {
            calledMyMethod = true;
            //method logic
        }           
    }
} 


Answer 3:

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


Answer 4:

据我了解你的问题,你需要在不可预知,但有限的时间间隔加载数据。 有三种可能的方式来做到这一点:1)你可以围绕你来loadSomeHeavyData呼叫与控制的访问方法的if语句。 2)你可以改变你的方法来处理控制流(决定更新与否)3)写一个更新线程,并让它为你做的工作,前两种方案可以使用一个外部的布尔值或使用产生boolean值决定最后一次通话UND当前调用时间之间的timedelta。 第三备选方案是那里每n秒/分钟定时线程并加载重的数据。



Answer 5:

我们写了包括图书馆实用懒洋洋地加载/调用一个方法。 它保证单次使用语义,并保留所有抛出的异常如你所期望。

用法很简单:

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() ,并等待生产者线程(第一个来电)来完成。



文章来源: How can I make sure a method is only called once by multiple threads?