-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathScopedValuesDemo.java
More file actions
137 lines (125 loc) · 4.91 KB
/
Copy pathScopedValuesDemo.java
File metadata and controls
137 lines (125 loc) · 4.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package Phase7_Concurrency.Multithreading;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* Scoped Values — Java 21 PREVIEW (JEP 446)
* -----------------------------------------
* A modern alternative to ThreadLocal for sharing IMMUTABLE per-call
* data with deeply nested code. Unlike ThreadLocal:
* <p>
*
* - Values are SCOPE-BOUND: they exist only inside a bounded scope
* and are automatically unbound on exit. No leaks across tasks.
* - Values are IMMUTABLE. There is no .set(value) once inside.
* If you want a different value, open a NESTED scope.
* - Cheap with millions of virtual threads: scoped values don't pay
* the per-thread map cost.
* - Inherited by child threads of a StructuredTaskScope automatically.
* <p>
*
* The API in comments (preview)
* -----------------------------
* public static final ScopedValue<String> USER = ScopedValue.newInstance();
* <p>
*
* ScopedValue.where(USER, "alice").run(() -> {
* // anywhere in here, USER.get() == "alice"
* callDeep();
* });
* <p>
*
* ScopedValue.where(USER, "alice").call(() -> doWork());
* <p>
*
* ScopedValue.where(USER, "alice")
* .where(LOCALE, Locale.US)
* .run(...); // bind several at once
* <p>
*
* Inside the body
* ---------------
* USER.get() - the value (throws if unbound)
* USER.isBound() - is there a value in scope?
* USER.orElse(defaultV) - safe accessor
* <p>
*
* Why preview, why care
* ---------------------
* Scoped Values are designed to be the per-call context of choice in a
* virtual-thread world. Each VT inherits the scope, not a private
* ThreadLocal map. They're a great fit for tracing, security
* principals, request IDs.
* <p>
*
* Implementation note for this repo
* ---------------------------------
* Because preview APIs require --enable-preview, this file shows the
* SHAPE in comments and provides a PORTABLE alternative (ThreadLocal +
* try/finally) that runs unchanged.
*/
public class ScopedValuesDemo {
/** Stand-in: ThreadLocal version (portable). */
private static final ThreadLocal<String> USER = new ThreadLocal<>();
public static void main(String[] args) throws Exception {
section("1) Portable equivalent — ThreadLocal binding scope");
runAs("alice", () -> {
doWork();
// nested binding
runAs("alice-impersonating-bob", ScopedValuesDemo::doWork);
doWork(); // restored to "alice"
});
section("2) The preview form (Java 21 with --enable-preview)");
// public static final ScopedValue<String> USER = ScopedValue.newInstance();
//
// ScopedValue.where(USER, "alice").run(() -> {
// System.out.println("user = " + USER.get());
// ScopedValue.where(USER, "bob").run(() -> {
// System.out.println("nested user = " + USER.get());
// });
// System.out.println("back to " + USER.get());
// });
System.out.println("(see comment above)");
section("3) Inheritance with virtual threads (preview)");
// ScopedValue.where(USER, "alice").run(() -> {
// try (var vts = Executors.newVirtualThreadPerTaskExecutor()) {
// vts.submit(() -> System.out.println("child sees " + USER.get())).get();
// }
// });
// PORTABLE FALLBACK: ThreadLocal is NOT inherited by children of an
// executor by default. With InheritableThreadLocal you'd inherit
// from the *submitting* thread but not from per-task wrappers.
runAs("alice", () -> {
ExecutorService vts = Executors.newVirtualThreadPerTaskExecutor();
try {
Future<?> f = vts.submit(() -> System.out.println("child sees USER = " + USER.get()));
f.get();
} catch (Exception e) { System.out.println("err: " + e.getMessage()); }
finally { vts.shutdown(); }
});
section("done");
}
/**
* Helper that emulates ScopedValue.where(USER, name).run(body).
* <p>
*
* It SETS the ThreadLocal, runs the body, and RESTORES the previous
* value in a finally — guaranteeing scope-bound cleanup.
*/
private static void runAs(String name, Runnable body) {
String previous = USER.get();
USER.set(name);
try { body.run(); }
finally {
if (previous == null) USER.remove();
else USER.set(previous);
}
}
private static void doWork() {
System.out.println(" inside work: USER = " + USER.get());
}
private static void section(String title) {
System.out.println();
System.out.println("=== " + title + " ===");
}
}