Add two standalone modules: Stream Gatherers (JEP 485) and Scoped Values vs ThreadLocal (JEP 506)
gatherers/: windowFixed, windowSliding, fold, scan, mapConcurrent, a custom Gatherer (dedupeConsecutive, takeUntil), and a Collector-vs-Gatherer comparison. Verified: windowSliding emits one short window on a too-short stream rather than shrinking to empty like windowFixed; mapConcurrent waits for in-flight siblings to finish (~901ms) rather than cancelling them when one mapper throws. scoped-values/: ScopedValue API and rebinding, why plain threads (virtual or platform) do not inherit a binding while a StructuredTaskScope subtask does, and the memory cost vs InheritableThreadLocal measured two ways -- a noisy heap-delta first pass, then a precise jcmd GC.class_histogram object census (5 shared Carrier objects vs ~700,000 copied ThreadLocalMap/Entry objects for 100,000 threads). Companion code for the ankurm.com posts "Java Stream Gatherers (JEP 485)" and "Scoped Values vs ThreadLocal in Java 25: Migration Guide with Virtual Threads". Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01FtpJvZfg4nvLvtzgJTDWpB
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.StructuredTaskScope;
|
||||
|
||||
/**
|
||||
* Parks N subtasks holding 5 bound values (mode "sv" = ScopedValue, mode "tl" =
|
||||
* InheritableThreadLocal) and prints READY, then sleeps for 60s so an external `jcmd <pid>
|
||||
* GC.class_histogram` can be run against it while every subtask is alive and holding its
|
||||
* bindings. scripts/scoped-values.sh drives this and diffs the two histograms -- see
|
||||
* docs/output/99-histogram-scopedvalue.txt and 99-histogram-threadlocal.txt.
|
||||
*
|
||||
* ScopedValueVsThreadLocalMemory.java measures the SAME comparison a coarser way (total heap
|
||||
* delta before/after) and is honest about that measurement being noisy relative to the fixed cost
|
||||
* of a VirtualThread + its StackChunk. This file exists because the coarse measurement alone
|
||||
* would undersell a real, structural difference that a precise object count makes obvious.
|
||||
* Chapter: docs/17-scoped-values-migration.md
|
||||
*/
|
||||
public class MemoryFootprintProbe {
|
||||
|
||||
static final ScopedValue<String> SV1 = ScopedValue.newInstance();
|
||||
static final ScopedValue<String> SV2 = ScopedValue.newInstance();
|
||||
static final ScopedValue<String> SV3 = ScopedValue.newInstance();
|
||||
static final ScopedValue<String> SV4 = ScopedValue.newInstance();
|
||||
static final ScopedValue<String> SV5 = ScopedValue.newInstance();
|
||||
|
||||
static final ThreadLocal<String> TL1 = new InheritableThreadLocal<>();
|
||||
static final ThreadLocal<String> TL2 = new InheritableThreadLocal<>();
|
||||
static final ThreadLocal<String> TL3 = new InheritableThreadLocal<>();
|
||||
static final ThreadLocal<String> TL4 = new InheritableThreadLocal<>();
|
||||
static final ThreadLocal<String> TL5 = new InheritableThreadLocal<>();
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String mode = args[0];
|
||||
int n = Integer.parseInt(args[1]);
|
||||
CountDownLatch ready = new CountDownLatch(n);
|
||||
CountDownLatch release = new CountDownLatch(1);
|
||||
System.out.println("PID=" + ProcessHandle.current().pid() + " mode=" + mode + " n=" + n);
|
||||
|
||||
if (mode.equals("sv")) {
|
||||
ScopedValue.where(SV1, "a").where(SV2, "b").where(SV3, "c").where(SV4, "d").where(SV5, "e").run(() -> {
|
||||
try (var scope = StructuredTaskScope.open()) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
scope.fork(() -> {
|
||||
SV1.get();
|
||||
ready.countDown();
|
||||
release.await();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
ready.await();
|
||||
System.out.println("READY sv");
|
||||
Thread.sleep(60_000);
|
||||
release.countDown();
|
||||
scope.join();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
TL1.set("a");
|
||||
TL2.set("b");
|
||||
TL3.set("c");
|
||||
TL4.set("d");
|
||||
TL5.set("e");
|
||||
try (var scope = StructuredTaskScope.open()) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
scope.fork(() -> {
|
||||
TL1.get();
|
||||
ready.countDown();
|
||||
release.await();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
ready.await();
|
||||
System.out.println("READY tl");
|
||||
Thread.sleep(60_000);
|
||||
release.countDown();
|
||||
scope.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user