How to implement thread-safe lazy initialization?

后端 未结 12 813
一整个雨季
一整个雨季 2020-11-28 04:13

What are some recommended approaches to achieving thread-safe lazy initialization? For instance,

// Not thread-safe
public Foo getI         


        
12条回答
  •  执念已碎
    2020-11-28 04:52

    Here is one more approach which is based on one-time-executor semantic.

    The full solution with bunch of usage examples can be found on github (https://github.com/ManasjyotiSharma/java_lazy_init). Here is the crux of it:

    “One Time Executor” semantic as the name suggests has below properties:

    1. A wrapper object which wraps a function F. In current context F is a function/lambda expression which holds the initialization/de-initialization code.
    2. The wrapper provides an execute method which behaves as:

      • Calls the function F the first time execute is called and caches the output of F.
      • If 2 or more threads call execute concurrently, only one “gets in” and the others block till the one which “got in” is done.
      • For all other/future invocations of execute, it does not call F rather simply returns the previously cached output.
    3. The cached output can be safely accessed from outside of the initialization context.

    This can be used for initialization as well as non-idempotent de-initialization too.

    import java.util.Objects;
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.atomic.AtomicBoolean;
    import java.util.concurrent.atomic.AtomicReference;
    import java.util.function.Function;
    
    /**
     * When execute is called, it is guaranteed that the input function will be applied exactly once. 
     * Further it's also guaranteed that execute will return only when the input function was applied
     * by the calling thread or some other thread OR if the calling thread is interrupted.
     */
    
    public class OneTimeExecutor {  
      private final Function function;
      private final AtomicBoolean preGuard;
      private final CountDownLatch postGuard;
      private final AtomicReference value;
    
      public OneTimeExecutor(Function function) {
        Objects.requireNonNull(function, "function cannot be null");
        this.function = function;
        this.preGuard = new AtomicBoolean(false);
        this.postGuard = new CountDownLatch(1);
        this.value = new AtomicReference();
      }
    
      public R execute(T input) throws InterruptedException {
        if (preGuard.compareAndSet(false, true)) {
          try {
            value.set(function.apply(input));
          } finally {
            postGuard.countDown();
          }
        } else if (postGuard.getCount() != 0) {
          postGuard.await();
        }
        return value();
      }
    
      public boolean executed() {
        return (preGuard.get() && postGuard.getCount() == 0);
      }
    
      public R value() {
        return value.get();
      }
    
    }  
    

    Here is a sample usage:

    import java.io.BufferedWriter;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.OutputStreamWriter;
    import java.io.PrintWriter;
    import java.nio.charset.StandardCharsets;
    
    /*
     * For the sake of this example, assume that creating a PrintWriter is a costly operation and we'd want to lazily initialize it.
     * Further assume that the cleanup/close implementation is non-idempotent. In other words, just like initialization, the 
     * de-initialization should also happen once and only once.
     */
    public class NonSingletonSampleB {
      private final OneTimeExecutor initializer = new OneTimeExecutor<>(
        (File configFile) -> {
          try { 
            FileOutputStream fos = new FileOutputStream(configFile);
            OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
            BufferedWriter bw = new BufferedWriter(osw);
            PrintWriter pw = new PrintWriter(bw);
            return pw;
          } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
          }
        }
      );  
    
      private final OneTimeExecutor deinitializer = new OneTimeExecutor<>(
        (Void v) -> {
          if (initializer.executed() && null != initializer.value()) {
            initializer.value().close();
          }
          return null;
        }
      );  
    
      private final File file;
    
      public NonSingletonSampleB(File file) {
        this.file = file;
      }
    
      public void doSomething() throws Exception {
        // Create one-and-only-one instance of PrintWriter only when someone calls doSomething().  
        PrintWriter pw = initializer.execute(file);
    
        // Application logic goes here, say write something to the file using the PrintWriter.
      }
    
      public void close() throws Exception {
        // non-idempotent close, the de-initialization lambda is invoked only once. 
        deinitializer.execute(null);
      }
    
    }
    

    For few more examples (e.g. singleton initialization which requires some data available only at run-time thus unable to instantiate it in a static block) please refer to the github link mentioned above.

提交回复
热议问题