Domanda

Sto cercando di rintracciare un problema nel nostro sistema e il seguente codice mi preoccupa. Quanto segue si verifica nel nostro metodo doPost () nel servlet primario (i nomi sono stati cambiati per proteggere i colpevoli):

...
if(Single.getInstance().firstTime()){
   doPreperations();
}
normalResponse();
...

Il singolo "Singolo" è simile al seguente:

private static Single theInstance = new Single();

private Single() {
...load properties...
}

public static Single getInstance() {
    return theInstance;
}

Con il modo in cui questo è impostato per usare un inizializzatore statico invece di controllare un valore theInstance nel metodo getInstance (), questo potrebbe essere ricostruito più volte?

PS: stiamo eseguendo WebSphere 6 con l'app su Java 1.4

È stato utile?

Soluzione

L'ho trovato sul sito di Sun:

  

Singleton multipli caricati contemporaneamente da caricatori di classi diverse

     

Quando due caricatori di classi caricano una classe,   in realtà hai due copie del   classe, e ognuno può avere il suo   Istanza Singleton. Questo è   particolarmente rilevante nei servlet   funzionante in alcuni motori servlet   (iPlanet per esempio), dove ciascuno   servlet per impostazione predefinita utilizza la propria classe   loader. Due servlet diversi   accedere a un Singleton congiunto, in   infatti, ottieni due diversi oggetti.

     

Caricatori di classi multiple si verificano di più   comunemente di quanto si possa pensare. quando   i browser caricano le classi dalla rete   per l'uso da parte di applet, usano a   caricatore di classi separato per ciascun server   indirizzo. Allo stesso modo, Jini e RMI   i sistemi possono usare una classe separata   caricatore per le diverse basi di codice   da cui scaricano file di classe.   Se il tuo sistema utilizza una classe personalizzata   caricatori, tutti gli stessi problemi possono   sorgere.

     

Se caricato da caricatori di classi differenti,   due classi con lo stesso nome, anche   lo stesso nome del pacchetto viene trattato come   distinti - anche se, in effetti, lo sono   byte per byte la stessa classe. Il   caricatori di classi diverse rappresentano   spazi dei nomi diversi che distinguono   classi (anche se le classi   i nomi sono gli stessi), in modo che i due   Le classi MySingleton sono in effetti   distinto. (Vedi " Caricatori di classi come a   Meccanismo dello spazio dei nomi " nelle risorse.)   Dal momento che appartengono a due oggetti Singleton   lo faranno due classi con lo stesso nome   appare a prima vista che ci sono   due oggetti Singleton dello stesso   Classe.

Citazione .

Oltre al problema precedente, se firstTime () non è sincronizzato, è possibile che si verifichino anche problemi di threading.

Altri suggerimenti

No, non verrà costruito più e più volte. È statico, quindi verrà costruito solo una volta, proprio quando la classe viene toccata per la prima volta dal Classloader.

Solo un'eccezione - se ti capita di avere più Classloader.

(da GeekAndPoke ):

alt text

Come altri hanno già detto, l'inizializzatore statico verrà eseguito una sola volta per classloader.

Una cosa che darei un'occhiata è il metodo firstTime () - perché il lavoro in doPreparations () non può essere gestito all'interno del singleton stesso?

Sembra una brutta serie di dipendenze.

Non c'è assolutamente alcuna differenza tra l'uso di un inizializzatore statico e l'inizializzazione lazy. In effetti è molto più facile confondere l'inizializzazione lazy, che impone anche la sincronizzazione. La JVM garantisce che l'inizializzatore statico viene sempre eseguito prima dell'accesso alla classe e accadrà una volta e una sola volta.

Detto questo JVM non garantisce che la tua classe verrà caricata una sola volta. Tuttavia, anche se viene caricato più di una volta, l'applicazione Web visualizzerà comunque solo il singleton rilevante, in quanto verrà caricata nel classloader dell'applicazione Web o nel relativo padre. Se hai distribuito diverse applicazioni Web, firstTime () verrà chiamato una volta per ogni applicazione.

La cosa più evidente da verificare è che firstTime () deve essere sincronizzato e che il flag firstTime sia impostato prima di uscire da quel metodo.

No, non creerà più copie di "Single". (Il problema con Classloader verrà visitato in seguito)

L'implementazione che hai descritto è descritta come "Eager Initialization" nel libro di Briant Goetz - " Java Competurrency in Practice '.

public class Single
{
    private static Single theInstance = new Single();

    private Single() 
    { 
        // load properties
    }

    public static Single getInstance() 
    {
        return theInstance;
    }
}

Tuttavia, il codice non è quello desiderato. Il codice sta tentando di eseguire l'inizializzazione lazy dopo la creazione dell'istanza. Ciò richiede che tutta la libreria client esegua 'firstTime () / doPreparation ()' prima di usarlo. Farai affidamento sul client per fare la cosa giusta che rende il codice molto fragile.

Puoi modificare il codice come segue in modo che non ci sia alcun codice duplicato.

public class Single
{
    private static Single theInstance = new Single();

    private Single() 
    { 
        // load properties
    }

    public static Single getInstance() 
    {   
        // check for initialization of theInstance
        if ( theInstance.firstTime() )
           theInstance.doPreparation();

        return theInstance;
    }
}

Sfortunatamente, questa è una cattiva implementazione dell'inizializzazione lazy e non funzionerà in un ambiente concorrente (come il contenitore J2EE).

Ci sono molti articoli scritti sull'inizializzazione di Singleton, in particolare sul modello di memoria. JSR 133 risolto molte debolezze della memoria Java modello in Java 1.5 e 1.6.

Con Java 1.5 e amp; 1.6, hai diverse scelte e sono menzionate nel libro " Java efficace " di Joshua Bloch.

  1. Eager Initialziation, come il precedente [EJ Item 3]
  2. Idioma della classe del titolare dell'inizializzazione pigra [EJ Item 71]
  3. Enum Type [EJ Item 3]
  4. Blocco a doppio controllo con campo statico "volatile" [EJ Item 71]

Le soluzioni 3 e 4 funzioneranno solo in Java 1.5 e versioni successive. Quindi la soluzione migliore sarebbe # 2.

Ecco l'implementazione di psuedo.

public class Single
{
    private static class SingleHolder
    {
        public static Single theInstance = new Single();
    }

    private Single() 
    { 
        // load properties
        doPreparation();
    }

    public static Single getInstance() 
    {
        return SingleHolder.theInstance;
    }
}

Nota che 'doPreparation ()' è all'interno del costruttore, quindi sei sicuro di ottenere l'istanza correttamente inizializzata. Inoltre, stai ripristinando il caricamento della classe pigra di JVM e non hai bisogno di alcuna sincronizzazione 'getInstance ()'.

Una cosa che hai notato che il campo statico theInstance non è non 'finale'. L'esempio su Java Concurrency non ha 'final' ma EJ. Forse James può aggiungere più colore alla sua risposta su "classloader" e requisiti di "final" per garantire la correttezza,

Detto questo, ci sono un effetto collaterale che con l'uso di 'static final'. Il compilatore Java è molto aggressivo quando vede 'final statico' e cerca di integrarlo il più possibile. Questo è menzionato in un post sul blog di Jeremy Manson .

Ecco un semplice esempio.

file: A.java

public class A
{
    final static String word = "Hello World";
}

file: B.java

public class B
{
    public static void main(String[] args) {
        System.out.println(A.word);
    }
}

Dopo aver compilato sia A.java che B.java, si modifica A.java come segue.

file: A.java

public class A
{
    final static String word = "Goodbye World";
}

Ricompila 'A.java' e riesegui B.class. L'output che otterresti è

Hello World

Per quanto riguarda il problema del classloader, la risposta è sì, puoi avere più di un'istanza di Singleton in più classloader. Puoi trovare ulteriori informazioni su wikipedia . C'è anche un articolo specifico su Websphere .

L'unica cosa che cambierei sull'implementazione di Singleton (oltre a non usare affatto un Singleton) è rendere definitivo il campo dell'istanza. Il campo statico verrà inizializzato una volta, al caricamento della classe. Poiché le lezioni sono caricate pigramente, ottieni un'istanza pigra gratuitamente.

Naturalmente, se viene caricato da caricatori di classi separati, si ottengono più "singletons", ma questo è un limite di ogni linguaggio singleton in Java.

MODIFICA : i bit firstTime () e doPreparations () sembrano sospetti però. Non possono essere spostati nel costruttore dell'istanza singleton?

No: l'inizializzazione statica dell'istanza verrà eseguita una sola volta. Due cose da considerare:

  • Questo non è thread-safe (l'istanza non è "pubblicata" nella memoria principale)
  • Il tuo metodo firstTime è probabilmente chiamato più volte, a meno che non sia sincronizzato correttamente

In teoria sarà costruito solo una volta. Tuttavia, questo modello si interrompe in vari server applicazioni, dove è possibile ottenere più istanze di classi "singleton" (poiché non sono thread-safe).

Inoltre, il modello singleton è stato molto criticato. Vedi ad esempio Singleton ti amo, ma mi stai buttando giù

Questo verrà caricato solo una volta quando la classe viene caricata dal classloader. Questo esempio fornisce una migliore implementazione di Singleton, tuttavia è il più lento possibile e sicuro per i thread. Inoltre, funziona in tutte le versioni note di Java. Questa soluzione è la più portatile tra diversi compilatori e macchine virtuali Java.


public class Single {

private static class SingleHolder {
   private static final Single INSTANCE = new Single();
}

private Single() {
...load properties...
}

public static Single getInstance() {
    return SingleHolder.INSTANCE;
}

}

Alla classe interna non viene fatto riferimento prima (e quindi caricato non prima dal caricatore di classi) rispetto al momento in cui viene chiamato getInstance (). Pertanto, questa soluzione è thread-safe senza richiedere costrutti di linguaggio speciali (ovvero volatile e / o sincronizzato).

Non è obbligatorio che la singola istanza sia definitiva (non è affatto una buona idea, perché questo ti eviterà di cambiarne il comportamento usando altri schemi).

Nel codice seguente puoi vedere come viene istanziato una sola volta (la prima volta che chiami il costruttore)

data del pacchetto;

import java.util.Date;

USDateFactory di classe pubblica implementa DateFactory {     private static USDateFactory usdatefactory = null;

private USDateFactory () { }

public static USDateFactory getUsdatefactory() {
    if(usdatefactory==null) {
        usdatefactory = new USDateFactory();
    }
    return usdatefactory;
}

public String getTextDate (Date date) {
    return null;
}

public NumericalDate getNumericalDate (Date date) {
    return null;
}

}

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top