En jouant avec des tests unitaires pour une classe singleton hautement simultanée, je suis tombé sur le comportement étrange suivant (testé sur JDK 1.8.0_162):
private static class SingletonClass {
static final SingletonClass INSTANCE = new SingletonClass(0);
final int value;
static SingletonClass getInstance() {
return INSTANCE;
}
SingletonClass(int value) {
this.value = value;
}
}
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
System.out.println(SingletonClass.getInstance().value); // 0
// Change the instance to a new one with value 1
setSingletonInstance(new SingletonClass(1));
System.out.println(SingletonClass.getInstance().value); // 1
// Call getInstance() enough times to trigger JIT optimizations
for(int i=0;i<100_000;++i){
SingletonClass.getInstance();
}
System.out.println(SingletonClass.getInstance().value); // 1
setSingletonInstance(new SingletonClass(2));
System.out.println(SingletonClass.INSTANCE.value); // 2
System.out.println(SingletonClass.getInstance().value); // 1 (2 expected)
}
private static void setSingletonInstance(SingletonClass newInstance) throws NoSuchFieldException, IllegalAccessException {
// Get the INSTANCE field and make it accessible
Field field = SingletonClass.class.getDeclaredField("INSTANCE");
field.setAccessible(true);
// Remove the final modifier
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
// Set new value
field.set(null, newInstance);
}
Les 2 dernières lignes de la méthode main () ne sont pas d'accord sur la valeur d'INSTANCE - je suppose que JIT s'est débarrassé complètement de la méthode puisque le champ est statique final. La suppression du mot clé final rend les valeurs de sortie correctes du code.
Laissant de côté votre sympathie (ou son absence) pour les singletons et oubliant pendant une minute que l'utilisation de la réflexion comme celle-ci pose problème - est-ce que mon hypothèse est correcte en ce sens que les optimisations JIT sont à blâmer? Si tel est le cas - ceux-ci sont-ils limités aux champs finaux statiques uniquement?
static final
champ. En plus de cela, peu importe si ce hack de réflexion se brise en raison de JIT ou de la concurrence.