T
- The type of the JVM-wide variable. Must be loaded by a single class loader.public final class JvmWideVariable<T>
extends java.lang.Object
This class addresses the limitation of static variables in the presence of multiple class loaders. It is not uncommon to assume that a static variable is unique and a public static variable is accessible across the JVM. This assumption is correct as long as the defining class of the static variable is loaded by only one class loader. If the defining class is loaded multiple times by different class loaders, the JVM considers them as different classes, and this assumption will break (i.e., the JVM might contain multiple instances of supposedly the same static variable).
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.
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", "COUNT", TypeToken.of(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
.
It may be helpful to think of JvmWideVariable
as an object wrapper/converter: When
converting a static variable, whatever type we would use for the variable, we should use the same
type again for the JVM-wide variable wrapped by a JvmWideVariable
instance.
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 by a single class loader. In the
above example, Counter.COUNT
can access a JVM-wide variable as its type, AtomicInteger
, belongs to Java’s core classes and should never be loaded by a custom class
loader. If a JVM-wide variable’s type is loaded multiple times by different class loaders, it
might introduce some runtime casting exception as they are essentially different types.
(Therefore, using JvmWideVariable
is probably not yet a complete solution to the
singleton design pattern being broken in the presence of multiple class loaders.)
Since a JVM-wide variable is by definition a shared variable, the client of this class needs
to provide proper synchronization when accessing a JVM-wide variable outside of this class, for
example by using thread-safe types such as AtomicInteger
and
ConcurrentMap
, using (implicit or explicit) locks where the locks
need to work across class loaders, or using the doCallableSynchronized(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", "COUNT", TypeToken.of(AtomicInteger.class), 0);
public static void increaseCounter() {
doRunnableSynchronized(() -> {
COUNT++;
});
}
}
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.String group,
java.lang.String name,
java.lang.Class<T> type,
T defaultValue)
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,
T defaultValue)
Creates a
JvmWideVariable instance that can access a JVM-wide variable. |
Modifier and Type | Method and Description |
---|---|
<V> V |
doCallableSynchronized(java.util.concurrent.Callable<V> action)
Executes the given action, where the execution is synchronized on the JVM-wide variable (an
MBean).
|
void |
doRunnableSynchronized(java.lang.Runnable action)
Executes the given action, where the execution is synchronized on the JVM-wide variable.
|
<V> V |
doSupplierSynchronized(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.
|
java.lang.String |
getGroup() |
java.lang.String |
getName() |
void |
set(T value)
Sets a value to this JVM-wide variable.
|
public JvmWideVariable(@NonNull java.lang.String group, @NonNull java.lang.String name, @NonNull com.google.common.reflect.TypeToken<T> typeToken, @Nullable T defaultValue)
JvmWideVariable
instance that can access a JVM-wide variable.
The group, name, type, and default value of the JVM-wide variable resemble the (fully qualified) name of the defining class of a static variable, the name of the static variable, its type, and its default value.
This method creates the JVM-wide variable and initializes it with the default value if the variable does not yet exist.
A JVM-wide variable is uniquely defined by its group and name. The client should provide the same variable with the same type and default value. If the client provides a different type for a variable that already exists, this method will throw an exception. However, if the client provides a different default value for a variable that already exists, this method will simply ignore that value and will not throw an exception.
The type T
of the variable must be loaded by a single class loader. Currently,
this method requires the single class loader to be the bootstrap class loader.
Since the generic type T
does not exist at run time, the client needs to
explicitly pass that type via a Class
or a TypeToken
instance. This method
takes a (TypeToken
as it is more general (it supports capturing complex types such as
Map<K, V>
). If the given type is simple (can be represented fully by a Class
instance), the client can use the JvmWideVariable(String, String, Class, Object)
method instead.
group
- the group of the variablename
- the name of the variabletypeToken
- the type of the variabledefaultValue
- the default value of the variable, can be nullpublic JvmWideVariable(@NonNull java.lang.String group, @NonNull java.lang.String name, @NonNull java.lang.Class<T> type, @Nullable T defaultValue)
JvmWideVariable
instance that can access a JVM-wide variable. This method
will call JvmWideVariable(String, String, TypeToken, Object)
. See the javadoc of
that method for more details.@NonNull public java.lang.String getGroup()
@NonNull public java.lang.String getName()
@Nullable public T get()
public void set(@Nullable T value)
public <V> V doCallableSynchronized(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 actionpublic <V> V doSupplierSynchronized(java.util.function.Supplier<V> action)
doCallableSynchronized(Callable)
public void doRunnableSynchronized(java.lang.Runnable action)
doCallableSynchronized(Callable)