我有以下结构:
public void someMethod(){
//DO SOME STUFF
try{
doSomeProcessing();
}
catch (Exception e){
loadSomeHeavyData();
doSomeProcessing();
}
}
该方法someMethod
可以同时由多个线程调用。 该doSomeProcessing
可能会引发异常(它在后台,可能成为过时的使用一些数据)。
如果一个异常被抛出然后loadSomeHeavyData();
做一些耗时的任务,让我们说“更新”目前所有的数据,我能够调用doSomeProcessing();
。
问题:我如何才能确保loadSomeHeavyData();
只调用一次 ? 如果我把一些原子标志的进入loadSomeHeavyData();
然后,我不能肯定这时候应该清除。
我该如何解决这个问题? 只是注意:我不能修改doSomeProcessing();
因为它是一个外部API,我使用Decorator模式来使用它。
你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
}
你保证只有一个线程在同一时间进入。
为了保证该方法只调用一次,没有任何特殊的语言特征; 你可以创建一个由输入法的第一个线程设置为true boolean类型的静态变量。 当调用方法总是检查标志:
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)你可以围绕你来loadSomeHeavyData呼叫与控制的访问方法的if语句。 2)你可以改变你的方法来处理控制流(决定更新与否)3)写一个更新线程,并让它为你做的工作,前两种方案可以使用一个外部的布尔值或使用产生boolean值决定最后一次通话UND当前调用时间之间的timedelta。 第三备选方案是那里每n秒/分钟定时线程并加载重的数据。
我们写了包括图书馆实用懒洋洋地加载/调用一个方法。 它保证单次使用语义,并保留所有抛出的异常如你所期望。
用法很简单:
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()
,并等待生产者线程(第一个来电)来完成。