Design patterns: Le singleton

Le singleton est un design pattern (ou patron de conception si vous êtes franchouillard) qui permet de limiter l’instanciation d’une classe à un seul objet dans un système. C’est le design pattern le plus (mal) utilisé par les développeurs JAVA. Le principe de base du singleton est de bloquer l’accès au constructeur (en le mettant privé) et d’avoir une méthode qui retourne l’instance après l’avoir créée si elle n’existe pas déjà.

Le singleton représente des risques quand il est mal utilisé. En milieu multi-threadé, l’accès à l’instance peut causer des pertes de performance en cas de système synchronisé. Egalement, son utilisation à tort et à travers dans des classes XxxxxManager ou XxxxxUtils donne à certains des envies de mettre des baffes. La bonne pratique pour la création de ce genre de classe est toute simple: Ne le faites pas.

Lazy initialisation

Une méthode (trop) couramment utilisée pour créer le singleton. La création de l’instance est retardée jusqu’au moment où on en a rééllement besoin. Néanmoins, la méthode est synchronized. Il en résulte donc de piétres performances en environnement multithreadé. Si l’instance a besoin d’être accédée régulièrement, l’application pourra montrer d’importants ralentissements.

public class LazyInitialization {
	private static LazyInitialization instance = null;

	private LazyInitialization() {}

	public static synchronized LazyInitialization getInstance() {
		if (instance == null) {
			instance = new LazyInitialization();
		}
		return instance;
	}
}

Double checked locking

Une manière de limiter le problème du synchronized est de limiter la synchronisation au moment de la création. Néanmoins, celà ne fonctionne pas dans un environnement multithread. Il s’agit ici d’un bel exemple de “code juste” qui ignore ses fautes. Autrefois populaire, ce type de singleton est aujourd’hui considéré comme un antipattern.

public class DoubleCheckedLocking {
	private static volatile DoubleCheckedLocking instance = null;

	private DoubleCheckedLocking() { }

	public static DoubleCheckedLocking getInstance() {
		if (instance == null) {
			synchronized (DoubleCheckedLocking.class) {
				if (instance == null) {
					instance = new DoubleCheckedLocking();
				}
			}
		}
		return instance;
	}
}

Eager initialisation

Si le systéme doit utiliser une instance de manière très régulière, il est possible d’utiliser cette méthode. Il n’y a pas de synchronized, ce qui améliore les performances, et l’instance n’est créée que lorsque la classe est utilisée. L’instance est créée au chargement ce qui implique la présence en mémoire d’objet pas forcément utilisés et il n’y a pas de gestion des exceptions.

public class EagerInitialization {
	private static final EagerInitialization INSTANCE = new EagerInitialization();

	private EagerInitialization() { }

	public static EagerInitialization getInstance() {
		return INSTANCE;
	}
}

Static initialisation

Une variante est l’initialisation statique. La création dans un bloc statique permet la gestion des exceptions. Mais là encore, il y a en mémoire des objets qui ne sont pas encore utilisés.

public class StaticInitialization {
	private static final StaticInitialization instance;

	static {
		try {
			instance = new StaticInitialization();
		} catch (Exception e) {
			throw new RuntimeException("Error on singleton initialization", e);
		}
	}

	public static StaticInitialization getInstance() {
		return instance;
	}

	private StaticInitialization() {
	}
}

On demand holder

Cette version a été développée suivant les recherches de William Pugh de l’université du Maryland. Cette méthode fonctionne dans toutes les versions de Java et retarde au maximum l’initialisation et est totalement thread-safe sans nécessiter l’utilisation de volatile ou de synchronized. Il s’agit du pattern le plus populaire actuellement. La présence de la inner class permet de retarder la création de l’instance au moment de l’appel de la méthode getInstance().

public class OnDemandHolder {

	private OnDemandHolder() {
	}

	private static class SingletonHolder {
		private static final OnDemandHolder INSTANCE = new OnDemandHolder();
	}

	public static OnDemandHolder getInstance() {
		return SingletonHolder.INSTANCE;
	}
}

Enum

L’auteur d’Effective JavaJoshua Bloch a presenté une solution basée sur enums. D’après lui, “a single-element enum type is the best way to implement a singleton”. Cette version ne fonctionne qu’à partir de Java 1.5. Le fait que ce soit un enum nous assure que la valeur est instantiée une seule fois dans le programme et est accessible globalement. Néanmoins, il n’y a pas de possibilité de lazy initialisation avec cette technique.

public enum Singleton {
    INSTANCE;
    public void execute (String arg) {
        // [...] 
    }
}

source: github