[groovy] implement NotNullCachedComputableWrapper without blocking (IDEA-154705)
authorDaniil Ovchinnikov <daniil.ovchinnikov@jetbrains.com>
Thu, 14 Apr 2016 16:18:22 +0000 (19:18 +0300)
committerDaniil Ovchinnikov <daniil.ovchinnikov@jetbrains.com>
Fri, 15 Apr 2016 12:29:35 +0000 (15:29 +0300)
plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/util/NotNullCachedComputableWrapper.java

index adc6069eeda326de19e53b325e950a12827b057d..86f28f64de892c2f2f48292d072891151d587e21 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2015 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,12 +21,14 @@ import com.intellij.openapi.util.RecursionManager;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.TestOnly;
 
+import java.util.concurrent.atomic.AtomicReference;
+
 public class NotNullCachedComputableWrapper<T> implements NotNullComputable<T> {
 
   private static final RecursionGuard ourGuard = RecursionManager.createGuard(NotNullCachedComputableWrapper.class.getName());
 
-  private NotNullComputable<T> myComputable;
-  private volatile T myValue;
+  private volatile NotNullComputable<T> myComputable;
+  private final AtomicReference<T> myValueRef = new AtomicReference<>();
 
   public NotNullCachedComputableWrapper(@NotNull NotNullComputable<T> computable) {
     myComputable = computable;
@@ -35,27 +37,29 @@ public class NotNullCachedComputableWrapper<T> implements NotNullComputable<T> {
   @NotNull
   @Override
   public T compute() {
-    T result = myValue;
-    if (result != null) return result;
-
-    //noinspection SynchronizeOnThis
-    synchronized (this) {
-      result = myValue;
-      if (result == null) {
-        final RecursionGuard.StackStamp stamp = ourGuard.markStack();
-        result = myComputable.compute();
-        if (stamp.mayCacheNow()) {
-          myValue = result;
-          myComputable = null;  // allow gc to clean this up
-        }
+    while (true) {
+      T value = myValueRef.get();
+      if (value != null) return value;                // value already computed and cached
+
+      final NotNullComputable<T> computable = myComputable;
+      if (computable == null) continue;               // computable is null only after some thread succeeds CAS
+
+      final RecursionGuard.StackStamp stamp = ourGuard.markStack();
+      value = computable.compute();
+      if (stamp.mayCacheNow()) {
+        if (myValueRef.compareAndSet(null, value)) {  // try to cache value
+          myComputable = null;                        // if ok, allow gc to clean computable
+          return value;
+        }                                             // if not ok then another thread already set cached value
+      }
+      else {
+        return value;                                 // do not try to cache, just return value
       }
     }
-
-    return result;
   }
 
   @TestOnly
   public boolean isComputed() {
-    return myValue != null;
+    return myValueRef.get() != null;
   }
 }