T
- The type of the JVM-wide variable. Must be loaded once by a single class loader.public final class JvmWideVariable<T>
extends java.lang.Object
This class addresses the limitation of static variables with regard to their scopes: Static variables are unique per class loader of the class that defines them. When the defining class of a static variable is loaded multiple times by different class loaders, the variable cannot be accessed from another class loader.
JVM-wide variables, on the other hand, allow a class loaded by different class loaders to still reference the same variable. That is, changes to a JVM-wide variable made from a class loaded by one class loader can be seen from the same class loaded by a different class loader.
A JvmWideVariable
instance should typically be assigned to some static field of a
class, not to an instance field or a local variable within a method, since the actual JVM-wide
variable will not automatically be garbage-collected when it is no longer used, as one would have
expected from an instance field or a local variable.
The usage of this class is as follows. Suppose we previously used a static variable:
public final class Counter {
public static final AtomicInteger COUNT = new AtomicInteger(0);
}
We can then convert the static variable into a JVM-wide variable:
public final class Counter {
public static final JvmWideVariable<AtomicInteger> COUNT =
new JvmWideVariable<>(
my.package.Counter.class, "COUNT", AtomicInteger.class, new AtomicInteger(0));
}
Note that in the above example, Counter.COUNT
is still a static variable of Counter
, with the previously discussed limitation (not only the Counter
class but even
the JvmWideVariable
class itself might be loaded multiple times by different class
loaders). What has changed is that Counter.COUNT
is now able to access a JVM-wide
variable of type AtomicInteger
. (The type of the JVM-variable after the conversion is the
same as the type of the static variable before the conversion.)
Where the context is clear, it might be easier to refer to variables of type JvmWideVariable
as JVM-wide variables, although strictly speaking they are not, but through them
we can access JVM-wide variables.
Also note that the type of a JVM-wide variable must be loaded once by a single class loader. If a JVM-wide variable’s type is loaded multiple times by different class loaders, it may result in runtime casting exceptions as they are essentially different types.
Since a JVM-wide variable is by definition a shared variable, the users of this class need to
provide proper synchronization when working with the value of the JVM-wide variable, for example
by using thread-safe types for its value such as AtomicInteger
and ConcurrentMap
,
using (implicit or explicit) locks where the locks need to work across class loaders, or using
the executeCallableSynchronously(Callable)
method and the like provided by this class.
For example, suppose we have a static variable of a non-thread-safe type (e.g., Integer
) and we use a synchronized block when modifying the variable:
public final class Counter {
public static Integer COUNT = 0;
public static synchronized void increaseCounter() {
COUNT++;
}
}
Then, the converted JVM-wide implementation can be as follows:
public final class Counter {
public static final JvmWideVariable<Integer> COUNT =
new JvmWideVariable<>(my.package.Counter.class, "COUNT", Integer.class, 0);
public static void increaseCounter() {
COUNT.executeRunnableSynchronously(() -> {
COUNT.set(COUNT.get() + 1);
});
}
}
JVM-wide variables either can be kept alive for the entire JVM lifetime, or can be released at
a certain point (e.g., at the end of a test method). Releasing a JVM-wide variable requires: (1)
un-registering the variable from the JVM via the unregister()
method, and (2) un-linking
all references to the JvmWideVariable
instance that accesses it.
This class is thread-safe.
Modifier and Type | Class and Description |
---|---|
static interface |
JvmWideVariable.ValueWrapperMBean<T>
The MBean interface, as required by a standard MBean implementation.
|
Constructor and Description |
---|
JvmWideVariable(java.lang.Class<?> definingClass,
java.lang.String name,
java.lang.Class<T> type,
T initialValue)
Creates a
JvmWideVariable instance that can access a JVM-wide variable, similar to
JvmWideVariable(String, String, String, TypeToken, Supplier) . |
JvmWideVariable(java.lang.Class<?> definingClass,
java.lang.String name,
com.google.common.reflect.TypeToken<T> typeToken,
java.util.function.Supplier<T> initialValueSupplier)
Creates a
JvmWideVariable instance that can access a JVM-wide variable, similar to
JvmWideVariable(String, String, String, TypeToken, Supplier) . |
JvmWideVariable(java.lang.String group,
java.lang.String name,
java.lang.String tag,
com.google.common.reflect.TypeToken<T> typeToken,
java.util.function.Supplier<T> initialValueSupplier)
Creates a
JvmWideVariable instance that can access a JVM-wide variable. |
JvmWideVariable(java.lang.String group,
java.lang.String name,
com.google.common.reflect.TypeToken<T> typeToken,
java.util.function.Supplier<T> initialValueSupplier)
Creates a
JvmWideVariable instance that can access a JVM-wide variable, similar to
JvmWideVariable(String, String, String, TypeToken, Supplier) . |
Modifier and Type | Method and Description |
---|---|
<V> V |
executeCallableSynchronously(java.util.concurrent.Callable<V> action)
Executes the given action, where the execution is synchronized on the JVM-wide variable (not
the variable's value).
|
void |
executeRunnableSynchronously(java.lang.Runnable action)
Executes the given action, where the execution is synchronized on the JVM-wide variable.
|
<V> V |
executeSupplierSynchronously(java.util.function.Supplier<V> action)
Executes the given action, where the execution is synchronized on the JVM-wide variable.
|
T |
get()
Returns the current value of this JVM-wide variable.
|
void |
set(T value)
Sets a value to this JVM-wide variable.
|
java.lang.String |
toString() |
void |
unregister()
Unregisters the JVM-wide variable from the JVM.
|
public JvmWideVariable(@NonNull java.lang.String group, @NonNull java.lang.String name, @NonNull java.lang.String tag, @NonNull com.google.common.reflect.TypeToken<T> typeToken, @NonNull java.util.function.Supplier<T> initialValueSupplier)
JvmWideVariable
instance that can access a JVM-wide variable. If the
JVM-wide variable does not yet exist, this constructor creates the variable and initializes
it with an initial value.
A JVM-wide variable is uniquely defined by its group, name, and tag. Typically, a JVM-wide variable should be assigned to a static field of a class. In that case, the group of the variable is usually the fully qualified name of the defining class of the static field. The tag of the variable is used to separate variables which have the exact same group and name but should not be shared (e.g., if the JVM loads different versions of the code and the types of the variables have changed across those versions).
A JVM-wide variable has a type and an initial value. The type T
of a JVM-wide
variable must be loaded once by a single class loader, to avoid runtime casting exceptions.
Currently, this constructor requires that single class loader to be the bootstrap class
loader.
If the users of this class provide a different type for a variable that already exists, it will also result in runtime casting exceptions. However, if they provide a different initial value for a variable that already exists, this constructor will simply ignore that value.
The users need to explicitly pass type T
via a TypeToken
or a Class
instance. This constructor takes a (TypeToken
as it is more general (it can
capture complex types such as Map<K, V>
). If the type is simple (can be represented
fully by a Class
instance), the users can use some other constructor instead.
group
- the group of the variablename
- the name of the variabletag
- the tag of the variabletypeToken
- the type of the variable, which must be loaded by the bootstrap class loaderinitialValueSupplier
- the supplier that produces the initial value of the variable. It
is called only when the variable is first created. The supplied value can be null.public JvmWideVariable(@NonNull java.lang.Class<?> definingClass, @NonNull java.lang.String name, @NonNull java.lang.Class<T> type, @Nullable T initialValue)
JvmWideVariable
instance that can access a JVM-wide variable, similar to
JvmWideVariable(String, String, String, TypeToken, Supplier)
. See the javadoc of
that constructor for more details.
This constructor can be used when:
JvmWideVariable
instance is defined.
Class
instance
instead of a TypeToken
instance.
IMPORTANT: This constructor should be used only when the value of the variable has the
exact type T
, not a sub-type of T
; otherwise, type T
alone would not
be sufficient to serve as a tag to capture the "uniqueness" of the variable.
definingClass
- the class in which this JvmWideVariable
instance is definedname
- the name of the variabletype
- the type of the variable, which must be loaded by the bootstrap class loaderinitialValue
- the initial value of the variable, can be null.JvmWideVariable(String, String, String, TypeToken, Supplier)
public JvmWideVariable(@NonNull java.lang.Class<?> definingClass, @NonNull java.lang.String name, @NonNull com.google.common.reflect.TypeToken<T> typeToken, @NonNull java.util.function.Supplier<T> initialValueSupplier)
JvmWideVariable
instance that can access a JVM-wide variable, similar to
JvmWideVariable(String, String, String, TypeToken, Supplier)
. See the javadoc of
that constructor for more details.
This constructor can be used when:
JvmWideVariable
instance is defined.
IMPORTANT: This constructor should be used only when the value of the variable has the
exact type T
, not a sub-type of T
; otherwise, type T
alone would not
be sufficient to serve as a tag to capture the "uniqueness" of the variable.
definingClass
- the class in which this JvmWideVariable
instance is definedname
- the name of the variabletypeToken
- the type of the variable, which must be loaded by the bootstrap class loaderinitialValueSupplier
- the supplier that produces the initial value of the variable. It
is called only when the variable is first created. The supplied value can be null.JvmWideVariable(String, String, String, TypeToken, Supplier)
public JvmWideVariable(@NonNull java.lang.String group, @NonNull java.lang.String name, @NonNull com.google.common.reflect.TypeToken<T> typeToken, @NonNull java.util.function.Supplier<T> initialValueSupplier)
JvmWideVariable
instance that can access a JVM-wide variable, similar to
JvmWideVariable(String, String, String, TypeToken, Supplier)
. See the javadoc of
that constructor for more details.
This constructor can be used when:
IMPORTANT: This constructor should be used only when the value of the variable has the
exact type T
, not a sub-type of T
; otherwise, type T
alone would not
be sufficient to serve as a tag to capture the "uniqueness" of the variable.
group
- the group of the variablename
- the name of the variabletypeToken
- the type of the variable, which must be loaded by the bootstrap class loaderinitialValueSupplier
- the supplier that produces the initial value of the variable. It
is called only when the variable is first created. The supplied value can be null.JvmWideVariable(String, String, String, TypeToken, Supplier)
@Nullable public T get()
public void set(@Nullable T value)
@Nullable public <V> V executeCallableSynchronously(@NonNull java.util.concurrent.Callable<V> action) throws java.util.concurrent.ExecutionException
This method is used to replace a static synchronized method operating on a static variable
when converting the static variable into a JVM-wide variable. (See the javadoc of JvmWideVariable
.)
java.util.concurrent.ExecutionException
- if an exception occurred during the execution of the action@Nullable public <V> V executeSupplierSynchronously(@NonNull java.util.function.Supplier<V> action)
executeCallableSynchronously(Callable)
public void executeRunnableSynchronously(@NonNull java.lang.Runnable action)
executeCallableSynchronously(Callable)
public void unregister()
Releasing a JVM-wide variable requires: (1) un-registering the variable from the JVM via
this method, and (2) un-linking all references to the JvmWideVariable
instance that
accesses it. Therefore, the users of this method typically need to also perform step (2) to
completely release the variable.
THREAD SAFETY: This method must be called only when the variable is not in use by any threads (e.g., at the end of a test method, when no other test is running). Otherwise, it would break the thread safety of this class.
public java.lang.String toString()
toString
in class java.lang.Object