¿Puede este singleton de Java ser reconstruido repetidamente en WebSphere 6?
Pregunta
Estoy intentando localizar un problema en nuestro sistema y el siguiente código me preocupa. Lo siguiente ocurre en nuestro método doPost () en el servlet primario (los nombres han sido cambiados para proteger al culpable):
...
if(Single.getInstance().firstTime()){
doPreperations();
}
normalResponse();
...
El 'Single' de singleton se ve así:
private static Single theInstance = new Single();
private Single() {
...load properties...
}
public static Single getInstance() {
return theInstance;
}
Con la forma en que está configurado para utilizar un inicializador estático en lugar de buscar una instancia nula en el método getInstance (), ¿podría reconstruirse una y otra vez?
PS: estamos ejecutando WebSphere 6 con la aplicación en Java 1.4
Solución
Encontré esto en el sitio de Sun:
Varios Singletons cargados simultáneamente por diferentes cargadores de clases
Cuando dos cargadores de clases cargan una clase, en realidad tienes dos copias de la clase, y cada uno puede tener su propio Instancia de singleton Es decir particularmente relevante en servlets funcionando en ciertos motores servlet (iPlanet por ejemplo), donde cada Servlet por defecto usa su propia clase cargador. Dos servlets diferentes Accediendo a un singleton conjunto lo haremos, en De hecho, consigue dos objetos diferentes.
Se producen más cargadores de clases múltiples comúnmente de lo que podría pensar. Cuando Los navegadores cargan clases desde la red. Para su uso por applets, utilizan un cargador de clases separado para cada servidor dirección. Del mismo modo, Jini y RMI. Los sistemas pueden usar una clase separada Cargador para las diferentes bases de código. desde donde se descargan archivos de clase. Si tu propio sistema usa clase personalizada cargadores, todos los mismos problemas pueden surgir.
Si es cargado por diferentes cargadores de clases, dos clases con el mismo nombre, incluso el mismo nombre del paquete, son tratados como distintas - incluso si, de hecho, son byte por byte de la misma clase. los diferentes cargadores de clase representan diferentes espacios de nombres que distinguen clases (aunque las clases los nombres son los mismos), por lo que los dos Las clases de
MySingleton
son de hecho distinto. (Ver " cargadores de clases como Mecanismo de espacio de nombres " en Recursos.) Dado que dos objetos Singleton pertenecen a dos clases del mismo nombre, lo hará Aparece a primera vista que hay dos objetos singleton de la misma clase.
Cita .
Además del problema anterior, si firstTime ()
no está sincronizado, también podría haber problemas de subprocesos allí.
Otros consejos
No, no se construirá una y otra vez. Es estático, por lo que solo se construirá una vez, justo cuando la clase es tocada por primera vez por el Classloader.
Única excepción: si tiene varios cargadores de clases.
(de GeekAndPoke ):
Como han mencionado otros, el inicializador estático solo se ejecutará una vez por cada cargador de clases.
Una cosa que echaría un vistazo es el método firstTime ()
. ¿Por qué no se puede manejar el trabajo en doPreparations ()
dentro del propio singleton?
Suena como un conjunto desagradable de dependencias.
No hay absolutamente ninguna diferencia entre usar un inicializador estático y una inicialización perezosa. De hecho, es mucho más fácil desordenar la inicialización perezosa, que también impone la sincronización. La JVM garantiza que el inicializador estático siempre se ejecute antes de que se acceda a la clase y sucederá una vez y solo una vez.
Dicho JVM no garantiza que su clase se cargará solo una vez. Sin embargo, incluso si se carga más de una vez, su aplicación web solo verá el singleton relevante, ya que se cargará en el cargador de clases de la aplicación web o en su principal. Si tiene varias aplicaciones web implementadas, se llamará a firstTime () una vez para cada aplicación.
Lo más evidente que hay que verificar es que firstTime () se debe sincronizar y que el indicador firstTime se establece antes de salir de ese método.
No, no creará varias copias de 'Único'. (El problema del cargador de clases se visitará más adelante)
La implementación que describió se describe como 'Inicialización impaciente' en el libro de Briant Goetz - ' Java Concurrency in Practice '.
public class Single
{
private static Single theInstance = new Single();
private Single()
{
// load properties
}
public static Single getInstance()
{
return theInstance;
}
}
Sin embargo, el código no es lo que querías. Su código está intentando realizar una inicialización perezosa después de crear la instancia. Esto requiere que toda la biblioteca del cliente realice 'firstTime () / doPreparation ()' antes de usarla. Vas a confiar en que el cliente haga lo correcto, lo que hace que el código sea muy frágil.
Puedes modificar el código de la siguiente manera para que no haya ningún código duplicado.
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;
}
}
Lamentablemente, esta es una implementación deficiente de la inicialización lenta y no funcionará en un entorno concurrente (como el contenedor J2EE).
Hay muchos artículos escritos sobre la inicialización de Singleton, específicamente en el modelo de memoria. JSR 133 solucionó muchas deficiencias en la memoria de Java Modelo en Java 1.5 y 1.6.
Con Java 1.5 & amp; 1.6, tiene varias opciones y se mencionan en el libro ' Java efectivo ' por Joshua Bloch.
- Inicialización impaciente, como la anterior [Artículo 3 de EJ]
- Idioma de la clase del titular de la inicialización perezosa [Artículo 71 de EJ]
- Tipo de enumeración [EJ Artículo 3]
- Bloqueo controlado doble con campo estático 'volátil' [Artículo 71 de EJ]
Las soluciones 3 y 4 solo funcionarán en Java 1.5 y superior. Así que la mejor solución sería # 2.
Aquí está la implementación 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;
}
}
Observe que 'doPreparation ()' está dentro del constructor, por lo que tiene la garantía de obtener la instancia correctamente inicializada. Además, estás volviendo a la carga de clases diferidas de JVM y no necesitas ninguna sincronización 'getInstance ()'.
Una cosa notó que el campo estático theInstance no es 'final'. El ejemplo en Java Concurrency no tiene 'final' pero EJ tiene. Tal vez James pueda agregar más color a su respuesta en el 'cargador de clases' y el requisito de 'final' para garantizar la corrección,
Habiendo dicho eso, hay un efecto secundario que con el uso de 'static static'. El compilador de Java es muy agresivo cuando ve "final estático" e intenta integrarlo lo más posible. Esto se menciona en una publicación de blog de Jeremy Manson .
Aquí hay un ejemplo simple.
archivo: A.java
public class A
{
final static String word = "Hello World";
}
archivo: B.java
public class B
{
public static void main(String[] args) {
System.out.println(A.word);
}
}
Después de compilar A.java y B.java, cambia A.java a la siguiente.
archivo: A.java
public class A
{
final static String word = "Goodbye World";
}
Usted recompila 'A.java' y vuelve a ejecutar B.class. La salida que obtendrías es
Hello World
En cuanto al problema del cargador de clases, la respuesta es sí, puede tener más de una instancia de Singleton en varios cargadores de clases. Puede encontrar más información en wikipedia . También hay un artículo específico en Websphere .
Lo único que cambiaría con respecto a esa implementación de Singleton (aparte de no usar un Singleton en absoluto) es hacer que el campo de instancia sea final. El campo estático se inicializará una vez, en la carga de clases. Dado que las clases se cargan de forma perezosa, efectivamente se obtiene una instanciación perezosa gratuita.
Por supuesto, si se carga desde cargadores de clases separados, obtienes múltiples "singletons", pero eso es una limitación de cada idioma de singleton en Java.
EDIT : los bits firstTime () y doPreparations () parecen sospechosos. ¿No se pueden mover al constructor de la instancia de singleton?
No: la inicialización estática de la instancia
solo se realizará una vez. Dos cosas a considerar:
- Esto no es seguro para la ejecución de subprocesos (la instancia no está " publicada " en la memoria principal)
- Su método
firstTime
probablemente se llame varias veces, a menos que esté debidamente sincronizado
En teoría, se construirá una sola vez. Sin embargo, este patrón se rompe en varios servidores de aplicaciones, donde puede obtener múltiples instancias de clases 'singleton' (ya que no son seguras para subprocesos).
También, el patrón de singleton ha sido muy criticado. Consulte, por ejemplo, Singleton te quiero, pero me estás derrumbando
Esto solo se cargará una vez cuando el cargador de clases cargue la clase. Este ejemplo proporciona una mejor implementación de Singleton, sin embargo, es lo más lento posible y seguro para subprocesos. Además, funciona en todas las versiones conocidas de Java. Esta solución es la más portátil entre los diferentes compiladores Java y máquinas virtuales.
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;
}
}
Se hace referencia a la clase interna no antes (y, por lo tanto, no se carga antes por el cargador de clases) desde el momento en que se llama a getInstance (). Por lo tanto, esta solución es segura para subprocesos sin requerir construcciones de lenguaje especiales (es decir, volátiles y / o sincronizadas).
No es obligatorio que la instancia individual sea definitiva (no es una buena idea, porque evitará que cambies su comportamiento usando otros patrones).
En el código a continuación, puede ver cómo se crea una instancia de una sola vez (la primera vez que llama al constructor)
fecha del paquete;
importar java.util.Date;
clase pública USDateFactory implementa DateFactory { USDATFactory estático privado usdatefactory = nulo;
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;
}
}