-
Bug
-
Resolution: Not an Issue
-
P4
-
None
-
None
-
generic
-
generic
ADDITIONAL SYSTEM INFORMATION :
Java 21 ff
A DESCRIPTION OF THE PROBLEM :
... to extend the use of ThreadLocal to the new world of virtual threads and
scoped and structured concurrency.
The simple idea behind:
let ThreadLocals use scoped values if bound, else fallback to normal mode
A new class or change/extend ThreadLocal-class itself:
public class ScopedThreadLocal<T>
extends ThreadLocal<T> {
public static <S> ScopedThreadLocal<S> withInitial(
final Supplier<? extends S> supplier) {
return new ScopedThreadLocal<>() {
@Override
protected S initialValue() {
return supplier.get();
}
};
}
private final ScopedValue<T> scopedValue;
public ScopedThreadLocal() {
super();
this.scopedValue = ScopedValue.newInstance();
}
public ScopedValue<T> getScopedValue() {
return scopedValue;
}
@Override
public T get() {
if (scopedValue.isBound()) {
return scopedValue.get();
}
return super.get();
}
}
First test looks promising:
static class CNT {
private static final AtomicInteger cnt = new AtomicInteger();
final String nam;
final int num;
CNT(final String name) {
super();
nam = Objects.requireNonNullElse(name, "CNT");
num = cnt.incrementAndGet();
}
public CNT() {
this(null);
}
String getName() {
return nam;
}
int getNum() {
return num;
}
@Override
public String toString() {
return "%1$s[%2$d]".formatted(nam, Integer.valueOf(num));
}
}
private static final ScopedThreadLocal<CNT> STL =
ScopedThreadLocal.withInitial(() -> new CNT());
private static ExecutorService es(final boolean virtual) {
if (virtual)
return Executors.newVirtualThreadPerTaskExecutor();
return Executors.newFixedThreadPool(5);
}
static void testScopedThreadLocal() {
final boolean virtual = "1".equals("1"); // --- change testmode
final boolean scoped = "1".equals("1"); // --- change testmode
final int tcnt = 50;
try (final ExecutorService es = es(virtual);) {
final Runnable r = () -> {
final Long tid = Long.valueOf(Thread.currentThread().threadId());
System.out.printf("main-thread %d: %s%n", tid, STL.get());
try (var scope = new StructuredTaskScope<>();) {
IntStream.range(0, 5).forEach(i -> {
scope.fork(() -> System.out.printf(" sub-thread %d / %d: %s%n",
tid, Integer.valueOf(i), STL.get()));
});
}
};
for (int i = 0; i < tcnt; i++) {
if (scoped)
es.submit(
() -> ScopedValue.where(STL.getScopedValue(), new CNT()).run(r));
else
es.submit(r);
}
}
System.out.println("instances used: " + CNT.cnt.get());
}
public static void main(final String[] args) {
testScopedThreadLocal();
}
Merry christmas!
Kurt
Java 21 ff
A DESCRIPTION OF THE PROBLEM :
... to extend the use of ThreadLocal to the new world of virtual threads and
scoped and structured concurrency.
The simple idea behind:
let ThreadLocals use scoped values if bound, else fallback to normal mode
A new class or change/extend ThreadLocal-class itself:
public class ScopedThreadLocal<T>
extends ThreadLocal<T> {
public static <S> ScopedThreadLocal<S> withInitial(
final Supplier<? extends S> supplier) {
return new ScopedThreadLocal<>() {
@Override
protected S initialValue() {
return supplier.get();
}
};
}
private final ScopedValue<T> scopedValue;
public ScopedThreadLocal() {
super();
this.scopedValue = ScopedValue.newInstance();
}
public ScopedValue<T> getScopedValue() {
return scopedValue;
}
@Override
public T get() {
if (scopedValue.isBound()) {
return scopedValue.get();
}
return super.get();
}
}
First test looks promising:
static class CNT {
private static final AtomicInteger cnt = new AtomicInteger();
final String nam;
final int num;
CNT(final String name) {
super();
nam = Objects.requireNonNullElse(name, "CNT");
num = cnt.incrementAndGet();
}
public CNT() {
this(null);
}
String getName() {
return nam;
}
int getNum() {
return num;
}
@Override
public String toString() {
return "%1$s[%2$d]".formatted(nam, Integer.valueOf(num));
}
}
private static final ScopedThreadLocal<CNT> STL =
ScopedThreadLocal.withInitial(() -> new CNT());
private static ExecutorService es(final boolean virtual) {
if (virtual)
return Executors.newVirtualThreadPerTaskExecutor();
return Executors.newFixedThreadPool(5);
}
static void testScopedThreadLocal() {
final boolean virtual = "1".equals("1"); // --- change testmode
final boolean scoped = "1".equals("1"); // --- change testmode
final int tcnt = 50;
try (final ExecutorService es = es(virtual);) {
final Runnable r = () -> {
final Long tid = Long.valueOf(Thread.currentThread().threadId());
System.out.printf("main-thread %d: %s%n", tid, STL.get());
try (var scope = new StructuredTaskScope<>();) {
IntStream.range(0, 5).forEach(i -> {
scope.fork(() -> System.out.printf(" sub-thread %d / %d: %s%n",
tid, Integer.valueOf(i), STL.get()));
});
}
};
for (int i = 0; i < tcnt; i++) {
if (scoped)
es.submit(
() -> ScopedValue.where(STL.getScopedValue(), new CNT()).run(r));
else
es.submit(r);
}
}
System.out.println("instances used: " + CNT.cnt.get());
}
public static void main(final String[] args) {
testScopedThreadLocal();
}
Merry christmas!
Kurt