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

时间:2012-11-13 07:00:52

标签: java multithreading oop concurrency

我有以下结构:

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

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

5 个答案:

答案 0 :(得分:11)

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

答案 1 :(得分:4)

使用synchronized关键字:

public synchronized void someMethod(){  
    //doStuff
}

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

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

public class MyClass {
    private static boolean calledMyMethod;

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

答案 2 :(得分: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。什么时候没有请求到达一个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;
    }
}

答案 3 :(得分:0)

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

答案 4 :(得分:0)

我们写了一个包含utility懒惰加载/调用方法的库。它保证了单一用法语义,并保留了您所期望的任何抛出异常。

用法很简单:

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()上阻塞,并等待生产者线程(第一个调用者)完成。