Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 39 additions & 2 deletions src/main/java/com/github/phantomthief/scope/Scope.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ public final class Scope {

private final ConcurrentMap<ScopeKey<?>, 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 {
Expand Down Expand Up @@ -190,8 +194,41 @@ public <T> void set(@Nonnull ScopeKey<T> key, T value) {
}
}

@SuppressWarnings("unchecked")
public <T> T get(@Nonnull ScopeKey<T> key) {
if (SCOPE_USE_SYNCHRONIZED) {
return getSynchronized(key);
}
return getNormal(key);
}

@SuppressWarnings("unchecked")
private <T> T getSynchronized(@Nonnull ScopeKey<T> 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> T getNormal(@Nonnull ScopeKey<T> key) {
T value = (T) values.get(key);
if (value == null && key.initializer() != null) {
// 这里不使用computeIfAbsent保证原子性,是因为computeIfAbsent会有几率造成同桶冲撞
Expand All @@ -210,4 +247,4 @@ public <T> T get(@Nonnull ScopeKey<T> key) {
}
return value == null ? key.defaultValue() : value;
}
}
}
Original file line number Diff line number Diff line change
@@ -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 <lixinyan@kuaishou.com>
* 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<Object> 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<String> scopeKey2 = allocate();
assertFalse(scopeKey2.set("def"));
assertNull(scopeKey2.get());
runWithNewScope(() -> {
assertTrue(scopeKey2.set("def"));
assertEquals(scopeKey2.get(), "def");
});
}
}
2 changes: 1 addition & 1 deletion src/test/java/com/github/phantomthief/scope/ScopeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Runnable> workQueue) {
Expand Down