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.
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", "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 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 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", "COUNT", Integer.class, 0);
public static void increaseCounter() {
COUNT.executeRunnableSynchronously(() -> {
COUNT.set(COUNT.get() + 1);
});
}
}
At the end of a build, JVM-wide variables usually need to be released (by un-registering the
variables from the JVM via the unregister()
method, and also un-linking all references
to them) since every build should be independent and the user may also change the plugin version
in between builds.
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,
java.util.function.Supplier<T> defaultValueSupplier)
Creates a
JvmWideVariable instance that can access a JVM-wide variable. |
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 (an
MBean).
|
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.
|
java.lang.String |
getGroup() |
java.lang.String |
getName() |
void |
set(T value)
Sets a value to this JVM-wide variable.
|
void |
unregister()
Unregisters the JVM-wide variable from the JVM.
|
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> defaultValueSupplier)
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.
The client needs to explicitly pass type T
via a Class
or a TypeToken
instance. This method 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 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 variabledefaultValueSupplier
- the supplier that produces the default 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.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, Supplier)
. 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)
@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()
IMPORTANT: This method should only be called lastly in a build or a test method, and
called only once from a single thread. Calling this method not at the end of a build or test
method or calling it multiple times or from multiple threads will break the thread safety of
this class. Any reference to this JvmWideVariable
instance needs to be unlinked as
well; otherwise, the next call on this JvmWideVariable
instance will throw an
exception because the JVM-wide variable no longer exists.