JEP 429, Extent-Local Variables (Incubator), was promoted from its JEP Draft 8263012 to Candidate status. This incubating JEP, under the umbrella of Project Loom, proposes enabling the sharing of immutable data within and across threads. This is preferred to thread-local variables, especially when using large numbers of virtual threads.
In this JEP, instead of ThreadLocal, a new type, ExtentLocal, is proposed. An extent-local variable allows data to be safely shared between components in a large program. Usually, it is declared as a final static field, so it can easily be reached from many components. It is written once, immutable, and available only for a bounded period during the thread's execution. Consider the following example:
class Server {
final static ExtentLocal<Principal> PRINCIPAL = new ExtentLocal<>();
void serve(Request request, Response response) {
var level = (request.isAdmin() ? ADMIN : GUEST);
var principal = new Principal(level);
ExtentLocal.where(PRINCIPAL, principal)
.run(() -> Application.handle(request, response));
}
}
class DBAccess {
DBConnection open() {
var principal = Server.PRINCIPAL.get();
if (!principal.canOpen()) throw new InvalidPrincipalException();
return newConnection();
}
}
Typically, large Java programs are composed of multiple components that share data. For example, a web framework may require server and data access components. The user authentication and authorization objects need to be shared across the components. The server component may create the object and then pass it as an argument to the method invocation. This method of passing arguments is not always viable because the server component may first call untrusted user code. ThreadLocal represents the available alternatives. Consider the following example using ThreadLocal:
class Server {
final static ThreadLocal<Principal> PRINCIPAL = new ThreadLocal<>();
public void serve(Request request, Response response) {
var level = (request.isAuthorized() ? ADMIN : GUEST);
var principal = new Principal(level);
PRINCIPAL.set(principal);
Application.handle(request, response);
}
}
class DBAccess {
DBConnection open() {
var principal = Server.PRINCIPAL.get();
if (!principal.canOpen()) throw new InvalidPrincipalException();
return newConnection();
}
}
In the above example, the PRINCIPAL object represents ThreadLocal, instantiated in the Server class, where the data is initially stored. Then, it is later used in the DBAccess class. Using the ThreadLocal variable, we avoid the server component calling a PRINCIPAL as a method argument when the server component calls user code, and the user code calls the data access component.
Although this approach looks compelling, it has numerous design flaws that are impossible to avoid:
Unconstrained mutability: Each thread-local variable is mutable. This means that a variable's get() and set() methods can be called at any time. The ThreadLocal API enables this to be supported. A general communication model in which data can flow in either direction between components, leads to a spaghetti-like data flow.
Unbounded lifetime: Memory leaks may occur in programs that rely on the unrestricted mutability of thread-local variables. Because developers often forget to call remove(), per-thread data is often retained for longer than necessary. It would be preferable if the writing and reading of per-thread data occurred within a limited timeframe during the thread's execution, thereby eliminating the possibility of leaks.
Expensive inheritance: When utilizing a large number of threads, the overhead of thread-local variables may increase because child threads can inherit thread-local variables from a parent thread. This can add a significant memory footprint.
With the availability of virtual threads (JEP 425), the problems of thread-local variables have become more pressing. Multiple virtual threads share the same carrier threads. This allows us to create a vast number of virtual threads. This means that a web framework can give each request its own virtual thread while simultaneously handling thousands or millions of requests.
In short, thread-local variables have more complexity than is usually needed for sharing data and come with high costs that cannot be avoided.
This JEP aims to solve all these problems with ThreadLocal and provide better alternatives.
Developers interested in discussing this new ExtentLocal class may visit this Reddit thread.