15

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.

user1950349
  • 4,738
  • 19
  • 67
  • 119
  • If you want to make sure that a piece of code is executed exactly once, than I think that putting it in an Enum's class static initializer is as close as you can get. JVM guarantees that this will be called at most once per classloader. But better than that, I don't think there is a clear way of giving such guarantee. Maybe if you gave more info about your use-case? – korolar Feb 05 '17 at 02:35
  • I have this method in one of my class which initializes all our metadata and once initialization is complete, then only I want to move forward in my application. – user1950349 Feb 05 '17 at 02:37
  • 1
    Wouldn't putting it in this class' static initializer be enough? – korolar Feb 05 '17 at 02:38
  • Why not make set which key's would be caller thread id? – MGorgon Feb 05 '17 at 02:44
  • 1
    I would use double-checked locking if instance level or the initialization-on-demand holder idiom if class level. I'd prefer the former and using Guava's `Suppliers.memoize()` to encapsulate the DCL logic. – Ben Manes Feb 05 '17 at 04:28

3 Answers3

14

@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.

Oliver Dain
  • 9,617
  • 3
  • 35
  • 48
  • Can you please add some explanation so that I can understand? Also will it take care of both my conditions? If you can explain basis on that, then it will help me to understand. I was doing some research and I thought I might have to use `CountDownLatch` along with `AtomicBoolean` as you suggested here? – user1950349 Feb 05 '17 at 02:54
  • Added an explanation. – Oliver Dain Feb 05 '17 at 03:19
  • 1
    I don't think this answer is completely satisfying the requirements. @user1950349 second requirement is not met. Here, won't all threads after the first will think this method _was_ run; but the method may _still be running_. All threads after the first must wait, which is where a `CountdownLatch` would be efficient. This [post](http://stackoverflow.com/questions/289434/how-to-make-a-java-thread-wait-for-another-threads-output) covers that approach -- @pdeva's answer in particular. – Keith Feb 05 '17 at 03:42
  • @Keith you are correct. Somehow I just overlooked that. Will update my answer. – Oliver Dain Feb 05 '17 at 04:09
2

you can create a static flag for your method which will only be changed once the method is invoked. The idea behind using the static flag is the fact that it do not belong to instance it belongs to class which means all thread created from same class will have access to same value of flag thus once the boolean value of flag is changed after first invocation of method all the other thread will be bypassed by if else conditional statement.

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

I hope this solve your query

siddhartha jain
  • 1,006
  • 10
  • 16
1

•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?

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

These lines of code GUARANTEE that initialize() would be called only once per thread, and since singleThreadLock is declared static, then your currently spawned JVM will just never allow any other Thread to gain access to the lock until the block inside synchronized is completely executed. Please also stay away from trying synchronized(this), as such statements could lead to severe concurrency issues.

ShayHaned
  • 358
  • 3
  • 8
  • Nothing is stopping multiple threads from _eventually_ calling this function. Read his second requirement. Some kind of `wasRun` variable needs to be set to true after `startBackgroundThread()` is called, and `wasRun` should then be evaluated at the beginning of the critical section. – Keith Feb 05 '17 at 03:49