Scoped value in Java
A scoped value is a value that may be safely and efficiently shared with methods without using method parameters.
Scoped values are a new type of variable in Java that are set once and are then available for reading for a bounded period of execution by a thread. Scoped values allow for safely and efficiently sharing data for a bounded period of execution without resorting to method arguments.
Declaration of Scoped Values
Scoped values are declared using the ScopedValue class. The ScopedValue class has a constructor that takes a single argument, which is the initial value of the scoped value. The ScopedValue class also has a get() method that returns the current value of the scoped value.
Example of Scoped values
A scoped value is a type of value that is only visible in a specific scope. In Java, scoped values are represented by the ScopedValue class.
To create a new scoped value, use the ScopedValue.newInstance() method. This method takes a single argument, which is the initial value of the scoped value.
Once you have created a scoped value, you can bind it to a specific scope. This is done by using the ScopedValue.where() method. The where() method takes two arguments: the first argument is the scoped value to bind, and the second argument is the scope to bind.
Once you have bound a scoped value to a scope, it will only be visible within that scope. This means that any code that is executed outside of that scope will not be able to access the scoped value.
Binding
private static final ScopedValueUSER = ScopedValue.newInstance(); ScopedValue.where(USER, "Sai", () -> doSomething());
Code executed within doSomething() that invokes USER.get() will read the value “Sai”. The scoped value is bound while executing doSomething() and becomes unbound when doSomething() completes (without or with an exception).
ScopedValue defines the where(ScopedValue, Object, Runnable) method to set the value of a ScopedValue for the bound time that a thread of the run method of the runnable is executed. Run's methods define a dynamic scope as they are executed in order. When the run method is finished (either normally or with an error), the scoped value is bound while it is being used in the dynamic scope. The ScopedValue get method is used to read the value of a variable in dynamic scope code.
Rebinding
For nested dynamic scopes, a new binding can be created using the ScopedValue API. Rebinding is a name for this. For the bounded execution of a method, a ScopedValue that is tied to one value may be bound to another new value. The method's code defines the nested dynamic scope as it is executed. The ScopedValue's value returns to its initial value when the method succeeds (normally or with an error).
In the above example, suppose that code executed by doSomething() binds USERNAME to a new value.
ScopedValue.where(USERNAME, "Saivardhan", () -> doMore());
Consider the following example to understand about binding.
public static ScopedValuescopedValue = ScopedValue.newInstance(); public static void main(String[] args) { ScopedValueTest instance = new ScopedValueTest(); ScopedValue.where(scopedValue, "Tim Nadella", () -> { System.out.println("Value is: " + scopedValue.get()); }); }
The above piece of code represents an example of binding.
Inheritance
Cross-thread data exchange is supported via ScopedValue. This sharing is allowed only in structured situations where a child thread is initiated and ends inside the parent thread's allotted execution time. When utilizing a StructuredTaskScope, scoped value bindings are recorded when constructing a StructuredTaskScope and are passed on to any threads launched in that scope using the fork function.
The ScopedValue USERNAME in the example below is bound to the value "Sai" to carry out a runnable action. The run method's code forks three child threads and generates a StructuredTaskScope. The values "Sai" will be read by code executed directly or indirectly by these threads while they are executing childTask1(), childTask2(), and childTask3().
private static final ScopedValueUSERNAME = ScopedValue.newInstance(); ScopedValue.where(USERNAME, "Sai", () -> { try (var scope = new StructuredTaskScope ()) { scope.fork(() -> childTask1()); scope.fork(() -> childTask2()); scope.fork(() -> childTask3()); ... } });
Let us see a Java program to implement Scoped Values.
File name: ScopedValues.java
import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; public class ScopedValues { private static final AtomicInteger co = new AtomicInteger(); public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 10; i++) { executorService.submit(new Callable() { @Override public String call() throws Exception { int value = co.getAndIncrement(); System.out.println("Scoped value: " + value); return String.valueOf(value); } }); } executorService.shutdown(); } }
Output:
Scoped value: 6 Scoped value: 7 Scoped value: 9 Scoped value: 3 Scoped value: 1 Scoped value: 0 Scoped value: 4 Scoped value: 5 Scoped value: 8 Scoped value: 2
Explanation:
The line static AtomicInteger named co. AtomicInteger is used for atomic operations on integers, ensuring that operations like increment are performed atomically without interference from other threads. The line creates an ExecutorService with a fixed thread pool size of 10. The ExecutorService is used for managing and controlling the execution of concurrent tasks. Inside a loop, 10 tasks are submitted to the ExecutorService. Each task is a Callable that increments the shared AtomicInteger (co) using getAndIncrement(). The current value is printed, and the String representation of the value is returned.
The line shuts down the ExecutorService after all tasks have been submitted.
Limitations:
Scoped values in Java have the following limitations:
- They require Java language support, which means you need to use special syntax and annotations to create and use them.
- They are not compatible with thread-local variables, which means you cannot mix them in your program.
- They are immutable, which means they do not support set methods for changing stored values.
- They are only available for a bounded period.
- They are not bound to a particular thread.