How to make sure that method is executed only once and from one thread only?

user1950349 :

I have a below method which I want to execute on below conditions:

  • This method should be executed only once. And once it is executed, it cannot be executed again so if anyone tried to execute again, it should return back by logging some useful error message already executed or anything useful.
  • And it should be executed by only one thread only. So if multiple threads are calling below method, then it should be called by only one thread and other threads should wait for initialization to complete?

Below is my method:

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

Is this possible to do? I am working with Java 7.

Oliver Dain :

@ShayHaned's solution uses locking. You can make it more efficient via AtomicBoolean like:

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

The above assumes you don't have to wait for the work in startBackgroundThread to complete. If you do, the solution becomes:

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

The reason this works is that AtomicBoolean.getAndSet(true) will, in one atomic operation, return the value that was previously set for and make the new value be true. So the first thread to get to your method will get false returned (since the variable was initialized to false) and it will, atomically, set it to true. Since that first thread had false returned it'll take the first branch in the if statement and your initialization will happen. Any other calls will find that wasRun.getAndSet returns true since the first thread set it to true so they'll take the 2nd branch and you'll just get the log message you wanted.

The CountDownLatch is initialized to 1 so all threads other than the first call await on it. They will block until the first thread calls countDown which will set the count to 0 releasing all the waiting threads.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=454575&siteId=1