FONTE: Java building
Inicialização e Lazy loading
Quando o singleton mapeia um recurso do SO ou até um recurso fisico ou periférico, é comum ser necessário vincular o objeto ao recurso através de algum processo de inicialização. Este processo em si mesmo pode ser demorado e consumir memória,processamento,ou requerer sincronismo entre threads diferentes. Por isso, é desejável que ele seja executado apenas quando necessário.
A inicialização pode acontecer no construtor, mas preferivelmente deveria acontecer em um método. Contudo, dado que a classe não pode ser extendida, não ha vantagem em usar um método para isto pois ele nunca será sobre-escrito. Isto não modifica o design anterior, pois basta colocar o codigo de inicialização dentro do construtor.
Para adiar o processo de inicialização é comum utilizar um processo conhecido como Lazy Loading e é comum ver implementações dele desta forma:
[code]public final class Desktop {
private static final Desktop ME;
public static Desktop getDesktop(){
if ( ME == null ){
ME = new Desktop();
}
return ME;
}
// Esconde o construtor.
private Desktop(){
//inicialização
}
// métodos normais
} [/code]
Código 3: Implementação comum de singleton com lazy loading
Só que esta forma de implementação tem vários problemas [1]. Por exemplo, não é determinista em ambientes multi-thread podendo levar à criação de mais do que um objecto. Isso leva a considerar que o método getDesktop deveria ser sincronizado e optariamos por uma versão onde adicionariamos a palavra synchronized ao método getDesktop.
Mas, analizando melhor, o que realmente interessa sincronizar é a criação do objeto. Uma vez criado não teremos mais problemas com o sincronismo. Esta conclusão leva a inplementar o método desta forma:
[code]public final class Desktop {
private static final Desktop ME;
public static Desktop getDesktop(){
if ( ME == null ){
synchronized(Desktop.class) {
ME = new Desktop();
}
}
return ME;
}
// o resto do codigo
} [/code]
Código 4: Implementação comum de singleton com lazy loading e sincronismo
Só que esta implementação, ainda não é suficiente para garantir sincronismo [2] o que leva as pessoas a criarem um anti-padrão desta forma:
[code]public final class Desktop {
private static final Desktop ME;
public static Desktop getDesktop(){
if ( ME == null ){
synchronized(Desktop.class) {
if ( ME == null ){
ME = new Desktop();
}
}
}
return ME;
}
// o resto do codigo
} [/code]
Código 5: Implementação comum de singleton com lazy loading e sincronismo com dupla verificação
A dupla verificação não funciona, embora pareça que sim, e constitui um anti-padrão grave. A solução real para este problema passa pelo uso da palavra reservada volatile.
[code]public final class Desktop {
private static final volatile Desktop ME;
public static Desktop getDesktop(){
if ( ME == null ){
synchronized(Desktop.class) {
if ( ME == null ){
ME = new Desktop();
}
}
}
return ME;
}
// o resto do codigo
}
[/code]
Código 6: Implementação comum de singleton com lazy loading e sincronismo com dupla verificação
Só que também esta solução era problemática proque nem todas as JVM implementavam o controle associado a volatile da mesma forma. Apenas após a versão 5 do Java, quando foi especificado o comportamento esperado associado a esta palavra é que se tornou seguro usá-la. Portanto, para código escrito em java 1.4 ou mais antigo, a solução é tornar todo o método sincronizado , para a versão 5 ou superior é utilizar a palavra volatile. Para singletons lógicos , em java 5 ou superior é melhor utilizar enum. Singletons lógicos são raros e provávelmente sempre os pode substituir pelo uso de outro padrão como Registry