如何确保该方法只执行一次并且仅从一个线程执行?

时间:2017-02-05 02:25:54

标签: java multithreading concurrency

我有一个以下方法,我想在以下条件下执行:

  • 此方法只能执行一次。一旦它被执行,它就不能再次执行,所以如果有人再次尝试执行,它应该通过记录一些有用的错误消息already executed或任何有用的东西返回。
  • 它应该只由一个线程执行。因此,如果多个线程正在调用下面的方法,那么它应该只由一个线程调用,而其他线程应该等待初始化完成?

以下是我的方法:

  public void initialize() {
    List<Metadata> metadata = getMetadata(true);
    List<Process> process = getProcess();
    if (!metadata.isEmpty() && !process.isEmpty()) {
        Manager.setAllMetadata(metadata, process);
    }
    startBackgroundThread();
  }

这可能吗?我正在使用Java 7。

3 个答案:

答案 0 :(得分:10)

@ ShayHaned的解决方案使用锁定。您可以通过AtomicBoolean来提高效率:

AtomicBoolean wasRun = new AtomicBoolean(false);
CountDownLatch initCompleteLatch = new CountDownLatch(1);

public void initialize() {
  if (!wasRun.getAndSet(true)) {
      List<Metadata> metadata = getMetadata(true);
      List<Process> process = getProcess();
      if (!metadata.isEmpty() && !process.isEmpty()) {
          Manager.setAllMetadata(metadata, process);
      }
      startBackgroundThread();
      initCompleteLatch.countDown();
  } else {
      log.info("Waiting to ensure initialize is done.");
      initCompleteLatch.await();
      log.warn("I was already run");
  }
}

以上假设您不必等待startBackgroundThread中的工作完成。如果这样做,解决方案就变成:

AtomicBoolean wasRun = new AtomicBoolean(false);
CountDownLatch initCompleteLatch = new CountDownLatch(1);

public void initialize() {
  if (!wasRun.getAndSet(true)) {
      List<Metadata> metadata = getMetadata(true);
      List<Process> process = getProcess();
      if (!metadata.isEmpty() && !process.isEmpty()) {
          Manager.setAllMetadata(metadata, process);
      }
      // Pass the latch to startBackgroundThread so it can
      // call countDown on it when it's done.
      startBackgroundThread(initCompleteLatch);
  } else {
      log.info("Waiting to ensure initialize is done.");
      initCompleteLatch.await();
      log.warn("I was already run");
  }
}

这样做的原因是AtomicBoolean.getAndSet(true)将在一个原子操作中返回先前设置的值,并使新值为true。因此,获取方法的第一个线程将返回false(因为变量初始化为false),并且原子地将其设置为true。由于第一个线程返回false,它将采用if语句中的第一个分支,并且您的初始化将会发生。任何其他调用都会发现wasRun.getAndSet返回true,因为第一个线程将其设置为true,因此他们将获取第二个分支,您将获得所需的日志消息。

CountDownLatch初始化为1,因此除了第一个调用await之外的所有线程。它们将阻塞,直到第一个线程调用countDown,这将把计数设置为0,释放所有等待的线程。

答案 1 :(得分:2)

您可以为方法创建静态标志,只有在调用方法后才会更改。使用静态标志背后的想法是它不属于它属于类的实例,这意味着从同一个类创建的所有线程都可以访问相同的标志值,因此一旦第一次调用后更改了标志的布尔值方法所有其他线程将被if else条件语句绕过。

 static boolen flag;
public void initialize() {
if (flag)
{// return from here or some message you want to generate 
}else{
    List<Metadata> metadata = getMetadata(true);
    List<Process> process = getProcess();
    if (!metadata.isEmpty() && !process.isEmpty()) {
        Manager.setAllMetadata(metadata, process);
    }
       flag = true;
    startBackgroundThread();       }}

我希望这能解决你的问题

答案 2 :(得分:1)

•它应该只由一个线程执行。因此,如果多个线程正在调用下面的方法,那么它应该只由一个线程调用,而其他线程应该等待初始化完成?

public static final Object singleThreadLock = new Object();

public void initialize()
{
    synchronized( singleThreadLock )
    {

        List<Metadata> metadata = getMetadata(true);
        List<Process> process = getProcess();
        if (!metadata.isEmpty() && !process.isEmpty()) 
        {
            Manager.setAllMetadata(metadata, process);
        }
       startBackgroundThread();
    } 
  }

初始化()的这些代码行GUARANTEE每个线程只调用一次,并且因为singleThreadLock被声明为静态,所以当前生成的JVM将永远不允许任何其他线程获得对锁的访问,直到块内部同步完全执行。请远离尝试synchronized(this),因为这样的语句可能会导致严重的并发问题。