Os inicializadores estáticos do Java são seguros para threads?

136

Estou usando um bloco de código estático para inicializar alguns controladores em um registro que tenho. Minha pergunta é, portanto, posso garantir que esse bloco de código estático seja absolutamente chamado apenas uma vez quando a classe for carregada pela primeira vez? Entendo que não posso garantir quando esse bloco de código será chamado, acho que é quando o Classloader o carrega pela primeira vez. Sei que poderia sincronizar a classe no bloco de código estático, mas acho que isso é realmente o que acontece, afinal?

Um exemplo de código simples seria;

class FooRegistry {

    static {
        //this code must only ever be called once 
        addController(new FooControllerImpl());
    }

    private static void addController(IFooController controller) { 
        // ...
    }
}

ou devo fazer isso;

class FooRegistry {

    static {
        synchronized(FooRegistry.class) {
            addController(new FooControllerImpl());
        }
    }

    private static void addController(IFooController controller) {
        // ...
    }
}
simon622
fonte
10
Não gosto desse design, pois não pode ser testado. Dê uma olhada na injeção de dependência.
Dfa

Respostas:

199

Sim, inicializadores estáticos Java são seguros para threads (use sua primeira opção).

No entanto, se você deseja garantir que o código seja executado exatamente uma vez, é necessário garantir que a classe seja carregada apenas por um único carregador de classes. A inicialização estática é executada uma vez por carregador de classes.

Matthew Murdoch
fonte
2
No entanto, uma classe pode ser carregado por várias classes de forquilha de modo AddController ainda pode ter chamado mais de uma vez (independentemente de haver ou não você sincronizar a chamada) ...
Matthew Murdoch
4
Ah, espere aí, então estamos dizendo que o bloco de código estático é realmente chamado para todos os carregadores de classes que carregam a classe. Hmm ... Eu acho que isso ainda deve estar ok, no entanto, im querendo saber como executar este tipo de código em um env OSGI iria funcionar, com classloaders pacote mulitple ..
simon622
1
Sim. O bloco de código estático é chamado para todos os carregadores de classes que carregam a classe.
Matthew Murdoch
3
@ simon622 Sim, mas operaria em um objeto de classe diferente em cada ClassLoader. Objetos de classe diferentes que ainda têm o mesmo nome completo, mas representam tipos diferentes que não podem ser convertidos um para o outro.
Erwin Bolwidt
1
isso significa que a palavra-chave 'final' é redundante no titular da instância em: en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom ?
Spc16670
11

Este é um truque que você pode usar para inicialização lenta

enum Singleton {
    INSTANCE;
}

ou para pré Java 5.0

class Singleton {
   static class SingletonHolder {
      static final Singleton INSTANCE = new Singleton();
   }
   public static Singleton instance() {
      return SingletonHolder.INSTANCE;
   }
}

Como o bloco estático no SingletonHolder será executado uma vez de maneira segura, você não precisará de nenhum outro bloqueio. A classe SingletonHolder só será carregada quando você chamar instance ()

Peter Lawrey
fonte
18
Você está baseando essa resposta no fato de que o bloco estático será executado apenas uma vez globalmente - que é a própria pergunta que foi feita.
Michael Myers
2
Eu acho que isso também não é seguro no ambiente de carregador de várias classes, o que dizer.?
Ahmad
2
Os ambientes do carregador @Ahmad multi-class foram projetados para permitir que cada aplicativo tenha seus próprios singletons.
Peter Lawrey
4

Em circunstâncias normais, tudo no inicializador estático acontece - antes de tudo que usa essa classe, portanto, a sincronização geralmente não é necessária. No entanto, a classe é acessível a qualquer coisa que o intiailizador estático chame (inclusive fazendo com que outros inicializadores estáticos sejam invocados).

Uma classe pode ser carregada por uma classe carregada, mas não necessariamente inicializada imediatamente. Obviamente, uma classe pode ser carregada por várias instâncias de carregadores de classes e, assim, tornar-se várias classes com o mesmo nome.

Tom Hawtin - linha de orientação
fonte
3

Sim, mais ou menos

Um staticinicializador é chamado apenas uma vez, portanto, por essa definição, ele é seguro para threads - você precisaria de duas ou mais invocações do staticinicializador para obter uma contenção de thread.

Dito isto, os staticinicializadores são confusos de muitas outras maneiras. Não há realmente nenhuma ordem especificada na qual eles são chamados. Isso fica realmente confuso se você tiver duas classes cujos staticinicializadores dependem um do outro. E se você usa uma classe, mas não usa o que o staticinicializador configurará, não há garantia de que o carregador de classes invocará o inicializador estático.

Por fim, lembre-se dos objetos nos quais você está sincronizando. Sei que isso não é realmente o que você está perguntando, mas verifique se a sua pergunta não está realmente perguntando se você precisa tornar o addController()thread safe.

Matt
fonte
5
Há uma ordem muito definida na qual eles são chamados: Por ordem no código-fonte.
Mafu 20/05
Além disso, eles sempre são chamados, não importa se você usa o resultado deles. A menos que isso foi mudado em Java 6.
Mafu
8
Dentro de uma classe, os inicializadores seguem o código. Dadas duas ou mais classes, não é tão definido como qual classe é inicializada primeiro, se uma classe é inicializada 100% antes de outra iniciar ou como as coisas são "intercaladas". Por exemplo, se duas classes cada uma tem inicializadores estáticos se referindo uma à outra, as coisas ficam feias rapidamente. Eu pensei que havia maneiras que você pode se referir a um static final int para outra classe w / o invocando os initializers mas eu não vou discutir o ponto de uma forma ou de outra
Matt
Fica feio, e eu evitaria. Mas existe uma maneira definida de como os ciclos são resolvidos. Citando "The Java Programming Language 4th Edition": Página: 75, Seção: 2.5.3. Inicialização estática: "Se ocorrerem ciclos, os inicializadores estáticos de X serão executados apenas no ponto em que o método de Y foi invocado. Quando Y, por sua vez, invoca o método X, esse método é executado com o restante dos inicializadores estáticos ainda a serem executados. "
JMI MADISON
0

Sim, inicializadores estáticos são executados apenas uma vez. Leia isto para mais informações .

Mike Pone
fonte
2
Não, eles podem ser executados mais de uma vez.
Expiação limitada
5
Não, eles podem ser executados uma vez por PER CLASSLOADER.
Ruurd
Resposta básica: O Static init é executado apenas uma vez. Resposta avançada: O init estático é executado uma vez por carregador de classes. O primeiro comentário é confuso porque o fraseado mistura essas duas respostas.
JMI MADISON
-4

Então, basicamente, como você deseja uma instância singleton, deve fazê-lo mais ou menos da maneira antiga e garantir que seu objeto singleton seja inicializado uma vez e apenas uma vez.

ruurd
fonte