From ed819b559cd837bc036e9199d27883ecf46d3190 Mon Sep 17 00:00:00 2001 From: lixinyan Date: Mon, 15 May 2023 11:42:54 +0800 Subject: [PATCH] Adding synchronized double-checked locking capability. --- .../com/github/phantomthief/scope/Scope.java | 41 +++++++++++- .../scope/ScopeGetSynchronizedTest.java | 67 +++++++++++++++++++ .../github/phantomthief/scope/ScopeTest.java | 2 +- 3 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 src/test/java/com/github/phantomthief/scope/ScopeGetSynchronizedTest.java diff --git a/src/main/java/com/github/phantomthief/scope/Scope.java b/src/main/java/com/github/phantomthief/scope/Scope.java index ad00ee0..cc07098 100644 --- a/src/main/java/com/github/phantomthief/scope/Scope.java +++ b/src/main/java/com/github/phantomthief/scope/Scope.java @@ -65,6 +65,10 @@ public final class Scope { private final ConcurrentMap, Boolean> enableNullProtections = new ConcurrentHashMap<>(); + static final String SCOPE_USE_SYNCHRONIZED_PARAM = "SCOPE_USE_SYNCHRONIZED"; + + private static final boolean SCOPE_USE_SYNCHRONIZED = Boolean.getBoolean(SCOPE_USE_SYNCHRONIZED_PARAM); + @Beta public static boolean fastThreadLocalEnabled() { try { @@ -190,8 +194,41 @@ public void set(@Nonnull ScopeKey key, T value) { } } - @SuppressWarnings("unchecked") public T get(@Nonnull ScopeKey key) { + if (SCOPE_USE_SYNCHRONIZED) { + return getSynchronized(key); + } + return getNormal(key); + } + + @SuppressWarnings("unchecked") + private T getSynchronized(@Nonnull ScopeKey key) { + T value = (T) values.get(key); + if (value == null && key.initializer() != null) { + synchronized (this) { + value = (T) values.get(key); + // computeIfAbsent会有几率造成同桶冲撞,java8之后会出现IllegalStateException recursive update + // 因此这里使用synchronized双重锁检验 + if (value == null && key.initializer() != null) { + if (enableNullProtections.containsKey(key)) { + return null; + } + value = key.initializer().get(); + if (value != null) { + values.put(key, value); + } else { + if (key.enableNullProtection()) { + enableNullProtections.put(key, true); + } + } + } + } + } + return value == null ? key.defaultValue() : value; + } + + @SuppressWarnings("unchecked") + public T getNormal(@Nonnull ScopeKey key) { T value = (T) values.get(key); if (value == null && key.initializer() != null) { // 这里不使用computeIfAbsent保证原子性,是因为computeIfAbsent会有几率造成同桶冲撞 @@ -210,4 +247,4 @@ public T get(@Nonnull ScopeKey key) { } return value == null ? key.defaultValue() : value; } -} +} \ No newline at end of file diff --git a/src/test/java/com/github/phantomthief/scope/ScopeGetSynchronizedTest.java b/src/test/java/com/github/phantomthief/scope/ScopeGetSynchronizedTest.java new file mode 100644 index 0000000..7b4dca9 --- /dev/null +++ b/src/test/java/com/github/phantomthief/scope/ScopeGetSynchronizedTest.java @@ -0,0 +1,67 @@ +package com.github.phantomthief.scope; + +import static com.github.phantomthief.scope.Scope.SCOPE_USE_SYNCHRONIZED_PARAM; +import static com.github.phantomthief.scope.Scope.runWithNewScope; +import static com.github.phantomthief.scope.ScopeKey.allocate; +import static com.github.phantomthief.scope.ScopeKey.withInitializer; +import static com.google.common.util.concurrent.MoreExecutors.shutdownAndAwaitTermination; +import static java.util.concurrent.TimeUnit.DAYS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.github.phantomthief.scope.ScopeTest.ScopeThreadPoolExecutor; + +/** + * @author lixinyan + * Created on 2023-05-16 + */ +public class ScopeGetSynchronizedTest { + + @BeforeAll + public static void setUp() { + System.setProperty(SCOPE_USE_SYNCHRONIZED_PARAM, "true"); + } + + @Test + void testGetSynchronized() { + AtomicInteger counter = new AtomicInteger(); + ScopeKey scopeKey = withInitializer(false, () -> { + try { + Thread.sleep(10); + counter.incrementAndGet(); + } catch (Exception ignored) { + + } + return "abc"; + }); + assertNull(scopeKey.get()); + assertEquals(0, counter.get()); + ExecutorService executorService = ScopeThreadPoolExecutor.newFixedThreadPool(100); + runWithNewScope(() -> { + IntStream.range(0, 100).forEach(i -> { + executorService.execute(() -> { + assertEquals("abc", scopeKey.get()); + }); + }); + shutdownAndAwaitTermination(executorService, 1, DAYS); + }); + assertEquals(1, counter.get()); + + ScopeKey scopeKey2 = allocate(); + assertFalse(scopeKey2.set("def")); + assertNull(scopeKey2.get()); + runWithNewScope(() -> { + assertTrue(scopeKey2.set("def")); + assertEquals(scopeKey2.get(), "def"); + }); + } +} diff --git a/src/test/java/com/github/phantomthief/scope/ScopeTest.java b/src/test/java/com/github/phantomthief/scope/ScopeTest.java index c878f14..b6d1ed5 100644 --- a/src/test/java/com/github/phantomthief/scope/ScopeTest.java +++ b/src/test/java/com/github/phantomthief/scope/ScopeTest.java @@ -272,7 +272,7 @@ private ExecutorService newBlockingThreadPool(int thread, String name) { return executor; } - private static class ScopeThreadPoolExecutor extends ThreadPoolExecutor { + static class ScopeThreadPoolExecutor extends ThreadPoolExecutor { ScopeThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) {