lunes, 28 de julio de 2008

Comparativa de rendimiento en la lectura de ficheros

A vueltas con el rendimiento en I/O :-)

Este artículo y comparación es tremendamente interesante, sobre todo porque está documentado con datos de benchmarking aunque sobre un hardware particular, ojo. Las conclusiones son las de "casi siempre", pero para un tío oxidado como yo está bien que se meta la NIO entre las comparaciones. Y que se compare con C, que sigue ganando, je je.

Sólo pegaré aquí las conclusiones, el resto debe leerse en el artículo original:

How read files quickly

For the best Java read performance, there are four things to
remember:

  • Minimize I/O operations by reading an array at a time, not a byte
    at a time. An 8Kbyte array is a good size.
  • Minimize method calls by getting
    data an array at a time, not a byte at a time. Use array indexing to get at
    bytes in the array.
  • Minimize thread synchronization locks if you don't need
    thread safety. Either make fewer method calls to a thread-safe class, or use a
    non-thread-safe class like FileChannel and MappedByteBuffer.
  • Minimize data
    copying between the JVM/OS, internal buffers, and application arrays. Use
    FileChannel with memory mapping, or a direct or wrapped array ByteBuffer.
Si tuviera tiempo, me gustaría compararlo con una la versión "no sincornizada" de BufferedInputStream, para ver el impacto que en concreto introducen las regiones de exclusión mutua.

Ubicación original del artículo: http://nadeausoftware.com/articles/2008/02/java_tip_how_read_files_quickly

miércoles, 23 de julio de 2008

Java CAPS: Optimización de transmisiones de ficheros

Java CAPS es el acrónimo de "Java Composite Aplications Platform Suite". Es la suite completa de Sun aunando Integrador + ESB + EAI + BPEL PM + eTL + B2B. En realidad muchos de estos conceptos se difuminan unos con otros según quién los defina, así que es lógico que estén en una sola plataforma, aunque una organización tienda a usar sólo el 20-50% de su capacidad y potencia.

Y forma parte del Java Enterprise System, pero como producto independiente. y no es barato precisamente. Muy potente, pero muy desesperante muchas veces. Como sus competidores (Aqualogic, por ejemplo).

Como cualquier framework o integrador termendamente potente y tremendamente flexible, en muchos aspectos se ahorra tiempo de integración (con SAP, con SWIFT, con host, con MQ, con HL7......) y sobre todo de mantenimiento posterior, pero a cambio no esperemos el mejor rendimiento y tener el máximo control a bajo nivel. Toneladas de capas de encapsulación lo impiden.

Como otros entornos, Java CAPS es de esos que te permite hacer las cosas mal y bien. Y por defecto, la opción rápida es hacerlas mal. Y la opción "no inmediata" es hacerlas bien. Es decir, leches, todo en memoria.

Y por duplicado muchas veces. Para variar.

Este artículo habla de cómo implementar comunicaciones FTP/FTPS/SFTP de forma "optimizada" para ficheros enormes. Sin más comentarios:

Más información sobre Java CAPS en la web oficial de Sun:

sábado, 19 de julio de 2008

Oracle JRockit: malas noticias, se confirman las sospechas

(Actualizado el día 21 de julio y también el 19 de septiembre)

Aquí está la noticia esperada, aunque no por ello menos horrible para la comunidad. Léase detenidamente, no obstante el resumen ejecutivo es que JRockit no va a ser un producto independiente, sino que estará incluido en los paquetes y productos comerciales de Oracle (Weblogics, Fussions, y otros).

http://www.oracle.com/technology/software/products/jrockit/FAQ.html

Puntos más significativos:
  • Sólo los clientes de Oracle lo pueden utilizar en producción (no sé si incluye la base de datos o sólo los productos de App Server / SOA).
  • Eso sí, puedes usar ese JRockit licenciado para ejecutar otros productos / servidores de aplicaciones que no sean de Oracle.
  • Desaparece la "redistributable license" para las nuevas versiones de JRockit. Vamos que no puedes empaquetar tus productos con JRockit incluido.
  • Sí sigue siendo gratuito y descargable (http://www.oracle.com/technology/software/products/jrockit/index.html) para desarrollo y evaluación.
Es cierto que era una decisión previsible, conociendo la política comercial de Oracle. Y por otro lado es cierto que es discutible el beneficio en términos marketinianos y de posicionamiento de marca que implica tener un producto gratuito sobre el que dedicas toneladas de recursos internos de la compañía... ante eso hay dos posibilidades:
  • Abrir el producto para dedicar menos recursos a él y simbiosis con la comunidad "open Source"en esa misma. Es lo que ha hecho Sun con OpenJDK 7 (es discultible la tibieza de esta política, pero bueno).
  • Cobrar por el producto o incluirlo en otros productos comerciales. Es lo que ha hecho Oracle.
Vamos, y haciendo de "oráculo" ("Oracle" en inglés si se me permite el chiste fácil): R.I.P y también supondrá a largo plazo un frenazo a las mejoras en Hotspot, a no ser que se ponga las pilas la comunidad Open Source...

Como puede verse, a nivel personal estoy contrariado, porque creo que básicamente esto elimina la competencia que había "entre los dos grandes" que ya comentamos en http://serverperformance.blogspot.com/2008/06/sun-hotspot-vs-bea-jrockit.html. Bueno, quizás IBM vuelva a recoger el guante pero lo dudo porque su política actual es un híbrido: JDK sin limitaciones para Linux, AIX y z/OS, pero en Windows sólo está disponible el JRE y sólo para máquinas IBM -controlado en BIOS- excepto un par de licencias para tests con Eclipse y para Apache Harmony... Para más información ver http://www.ibm.com/developerworks/java/jdk/.

En fin, tampoco gritemos esto muy alto porque los resultados empresariales de Sun en este semestre han sido horribles y hay rumores sobre el descontento de los accionistas sobre la política de "software gratis significa beneficios con el hardware" y quizás vientos de cambio que vete a saber...

Corolario: Al final va a resultar que la recomendación que hice en la entrada enlazada arriba ("(...) mi opinión es que hoy en día hay que ser pragmático: si vas a ejecutar un Weblogic / Aqualogic, móntalo sobre JRockit; y si vas a ejecutar un Glassfish / JES / Java CAPS, hazlo sobre la JVM de Sun.") va a dejar de ser una recomendación sino una obligación, un mapa oficial de dependencias entre productos, licencias y hardware/software base. Es decir, habrá un par de JDKs abiertos y libres (Open JDK por un lado y Apache Harmony por el otro) y después cada sistema operativo y/o servidor de aplicaciones tendrá asociado un JDK específico de ese mismo fabricante. Y en realidad de facto ya es ese el panorama como hablábamos, véase los JDK de HP e IBM específicos para para ciertos sistemas operativos, y los JDK específicos para ciertos servidores de aplicaciones. Sólo falta que Sun tenga dos versiones de su JDK et voilà..

Aclaración: Ojo que no soy ningún fan de Sun ni mucho menos, pero mira por lo menos me acaban de ahorrar tiempo para tomar cierta decisión...

P.D: Y otro chiste fácil y viejo... "ORACLE" al revés se lee "EL CARO" :-) [For those using automatic translators: this is a pun in Spanish: "O-R-A-C-L-E" => "E-L-C-A-R-O", meaning "THE EXPENSIVE ONE"]



Actualización 19/9/2008: Unos meses después, parece que las aguas han vuelto a su cauce y se ha normalizado la política de Oracle respecto a los productos de Bea, al menos vuelve a haber blogs corporativos y la gente de JRockit parece activa y trabajando en distintas líneas nuevas...

lunes, 14 de julio de 2008

Buffers inteligentes con swap a disco, o "a prueba de tontos"

Entramos en modo paja mental, siguiendo lo que introduje en la anterior entrada "Buffers: por defecto y por exceso".

De forma recurrente, vienen a mi dos necesidades / problemas típicos, que pueden resolverse de forma elegante a la vez. Dos por uno. Mierda para cada uno.

A saber:

  1. Dado un OutputStream sobre el que mi código va escribiendo quiero ser capaz de obtener de forma elegante el InputStream. Sin mucha sintaxis, sin crear programáticamente ficheros temporales o hacer cosas raras con arrays de bytes. Ni con extraños modelos de pipelining. Pensando en un uso single-thread. ¿Por qué? Porque yo lo valgo. Y porque Murphy dice que si tienes un OutputStream invocarás a un método que necesite como parámetro un InputStream. Y viceversa.
  2. Y mucho más importante: ¿cuántas veces hemos dicho lo malos malísimos que son los arrays de bytes? ¿es que no somos conscientes de que potencialmetne son un cúmulo de bichos? ¿que se volverán contra nosotros en cualquier momento del futuro, cuando estamos pensando ya en otras cosas?

Es inimaginable cómo se abusa en general del uso de array de bytes. Y no me refiero sólo a los típicos gazapos de un programador que empieza. Me estoy refiriendo a diseños internos de productos de reconocido prestigio tanto a nivel nacional o internacional (y no señalaré a nadie).

En alguna otra ocasión hemos hablado de que sería maravilloso que la gestión de memoria en Java soportara algún tipo de nuevo modelo de memoria, separada de los heaps y permgens y todo eso, sobre el que pudiera hacerse swap a disco sin problema. Sé que ya es suficientemente compleja (eden, survivor space, permanent) pero sería genial que la propia JVM detecte el uso de arrays / buffers grandes, o que fuera configurable, y que permitiera hacer swapping a disco de todo o parte de él independientemente de en qué zona de memoria resida.

Pero mientras no sea así, también podemos hacerlo nosotros. La encapsulación al poder. Dejo aquí un código fuente que hice en 2003, allá por los tiempos del JDK 1.3 y 1.4 (creo :-), que básicamene encapsula eso: funciona como un ByteArrayOutputStream de esos que gustan tanto a los programadores, pero en cuanto pasa de un cierto tamaño máximo, swapea a disco. Y además permite recoger un InputStream para hacer pipelines dentro de un solo thread. Por cierto, sin regiones de exclusión mutua porque está pensado para ser usado en un solo thread.

Es decir, un mecanismo "automático" para balancear alto rendimiento y alta escalabilidad: rendimiento en el tratamiento de streams cuando estos son de un tamaño aceptable (todo en memoria), y escalabilidad porque limita el abuso de la memoria como recurso en caso de que algún elemento se nos vaya de las manos.

¿Usos? Más de los que pueden parecer en un principio, por ejemplo intercambio de información entre procesos/colaboraciones desacopladas, para tratar optimizadamente peticiones SOAP con grandes ficheros como parámetros, generación y tratamiento de imágenes o PDFs o Excels en servidor, uso como elemento temporal "optimizado" por ejemplo en un GZIPFilter (si el tamaño comprimido es pequeño, se usa buffer en memoria, si es grande se usa buffer en disco para no consumir excesiva), etc.

Ahí va el código fuente:


package org.serverperformance.io;

import java.io.File;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.OutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.FileOutputStream;

/**
* Equivale a un ByteArrayOutputStream con las siguientes características especiales:
* - El buffer sólo consume 32 KBytes de heap (configurable, en caso de requerir más,
* utiliza el disco per nunca consume más heap que el marcado.
* - Métodos getInputStream() y writeTo(OutputStream) para facilitar las
* conversiones (típico al invocar métodos de JDBC que requieren InputStreams, mientras
* que la información se va escribiendo en un OutputStream
* - Se añade un método write(InputStream) que escribe en este stream de salida todo el
* contenido del stream de entrada.
* - A diferencia que ByteArrayOutputStream, el método close() también libera recursos en memoria.
*
* El código fuente está basado en java.io.BufferedOutputStream, con los añadidos
* necesarios y quitando las regiones de exclusión mutua.
*
* @author "Server Performance" http://serverperformance.blogspot.com
* @author (Basado en) http://java.sun.com
* @version 1.1, 30/06/03
*/

public class IntelligentBufferedOutputStream extends OutputStream {

private final static int DEFAULT_BUFFER_SIZE = 32*1024;
private final static String TEMP_FILE_PREFIX = "temp_buff_serverperformance_";
private final static String TEMP_FILE_SUFFIX = ".tmp";

/**
* The output stream to the underlying temp file.
*/

protected OutputStream out;
protected InputStream in;

/**
* The underlying temporal file.
*/

protected File tempFile;

protected boolean usedTempFile = false;

/**
* The internal buffer where data is stored.
*/

protected byte buf[];

/**
* The number of valid bytes in the buffer. This value is always
* in the range 0 through buf.length; elements
* buf[0] through buf[count-1] contain valid
* byte data.
*/

protected int count;

protected boolean deleteTempFileOnClose = true;

/**
* Creates a new buffered output stream to write data to the
* specified underlying output stream with a default 32-kbyte
* buffer size.
*
* @author "Server Performance" http://serverperformance.blogspot.com
*/

public IntelligentBufferedOutputStream() throws IOException {
this(DEFAULT_BUFFER_SIZE, true);
}

/**
* Creates a new buffered output stream to write data to the
* specified underlying output stream with a default buffer size.
*
* @author "Server Performance" http://serverperformance.blogspot.com
*/

public IntelligentBufferedOutputStream(boolean deleteTempFileOnClose) throws IOException {
this(DEFAULT_BUFFER_SIZE, deleteTempFileOnClose);
}

/**
* Creates a new buffered output stream to write data to the
* specified underlying output stream with the specified buffer
* size.
*
* @param size the buffer size.
* @exception IllegalArgumentException if size <= 0. * @author "Server Performance" http://serverperformance.blogspot.com
*/

public IntelligentBufferedOutputStream(int size, boolean deleteTempFileOnClose) throws IOException {
this.buf = new byte[size];
this.deleteTempFileOnClose = deleteTempFileOnClose;
}

/** Obtiene un InputStream, bien un BufferedInputStream del fichero temporal, bien
* un ByteArrayInputStream del buffer en memoria (según el caso).
*
* @author "Server Performance" http://serverperformance.blogspot.com
*/

public InputStream getInputStream() throws IOException {
if (in==null) {
if (usedTempFile) {
flushBufferToFile();
in = new BufferedInputStream(new FileInputStream(tempFile),buf.length);
}
else {
in = new ByteArrayInputStream(buf,0,count);
}
}
return in;
}

/** Copia el contenido del buffer (en memoria o en disco) a otro OutputStream.
*
* @author "Server Performance" http://serverperformance.blogspot.com
*/

public void writeTo(OutputStream target) throws IOException {
// El tamaño del buffer temporal... pues el mismo que el original
byte[] tempBuf = new byte[buf.length];
getInputStream();
int readedLen;
while ((readedLen = in.read(tempBuf)) > 0) {
target.write(tempBuf, 0, readedLen);
}
}

/** Flush the internal buffer
*
* @author "Server Performance" http://serverperformance.blogspot.com
*/

private void flushBufferToFile() throws IOException {
// Si no estaba creado, crea el fichero temporal
if (!usedTempFile) {
tempFile = File.createTempFile(TEMP_FILE_PREFIX,TEMP_FILE_SUFFIX,null);
try {
tempFile.deleteOnExit();
}
catch (Throwable ignored) {}
out = new FileOutputStream(tempFile);
usedTempFile = true;
}
if (count > 0) {
// Flush to disk!
out.write(buf, 0, count);
out.flush();;
count = 0;
}
}

/**
* Consumes from de specified InputStream and writes into this buffered output stream.
* ¡OJO! no cierra el inputstream, debe cerrarlo el invocante...
*
* @param in the origin/producer of the data.
* @exception IOException if an I/O error occurs.
*
* @author "Server Performance" http://serverperformance.blogspot.com
* @author (Basado en) http://java.sun.com
*/

public /*synchronized*/ void write(InputStream in) throws IOException {
byte[] bufLectura = new byte[buf.length];
int len;
while ((len = in.read(bufLectura)) > 0) {
write(bufLectura, 0, len);
}
}

/**
* Writes the specified byte to this buffered output stream.
*
* @param b the byte to be written.
* @exception IOException if an I/O error occurs.
*
* @author "Server Performance" http://serverperformance.blogspot.com
* @author (Basado en) http://java.sun.com
*/

public /*synchronized*/ void write(int b) throws IOException {
if (count >= buf.length) {
flushBufferToFile();
}
buf[count++] = (byte)b;
}

/**
* Writes len bytes from the specified byte array
* starting at offset off to this buffered output stream.
*
* Ordinarily this method stores bytes from the given array into this
* stream's buffer, flushing the buffer to the underlying output stream as
* needed. If the requested length is at least as large as this stream's
* buffer, however, then this method will flush the buffer and write the
* bytes directly to the underlying output stream. Thus redundant
* BufferedTempFileOutputStreams will not copy data unnecessarily.
*
* @param b the data.
* @param off the start offset in the data.
* @param len the number of bytes to write.
* @exception IOException if an I/O error occurs.
*
* @author "Server Performance" http://serverperformance.blogspot.com
* @author (Basado en) http://java.sun.com
*/

public /*synchronized*/ void write(byte b[], int off, int len) throws IOException {
if (len >= buf.length) {
/* If the request length exceeds the size of the output buffer,
flush the output buffer and then write the data directly.
In this way buffered streams will cascade harmlessly. */
flushBufferToFile();
out.write(b, off, len);
return;
}
if (len > buf.length - count) {
flushBufferToFile();
}
System.arraycopy(b, off, buf, count, len);
count += len;
}

/**
* Flushes this buffered output stream. This forces any buffered
* output bytes to be written out to the underlying output stream.
*
* @exception IOException if an I/O error occurs.
* @see java.io.OutputStream#out
*
* @author "Server Performance" http://serverperformance.blogspot.com
* @author (Basado en) http://java.sun.com
*/

public /*synchronized*/ void flush() throws IOException {
if (usedTempFile) {
flushBufferToFile();
out.flush();
}
}

/**
* Writes b.length bytes to this output stream.
*
* The write method of OutputStream
* calls its write method of three arguments with the
* arguments b, 0, and
* b.length.
*
* Note that this method does not call the one-argument
* write method of its underlying stream with the single
* argument b.
*
* @param b the data to be written.
* @exception IOException if an I/O error occurs.
* @see java.io.OutputStream#write(byte[], int, int)
*
* @author "Server Performance" http://serverperformance.blogspot.com
* @author (Basado en) http://java.sun.com
*/

public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}

/**
* Closes this output stream and releases any system resources
* associated with the stream, including the memory buffer.
*
Also, delete the temporal file if so marked.
*
* @exception IOException if an I/O error occurs.
* @see java.io.OutputStream#flush()
* @see java.io.OutputStream#out
*
* @author "Server Performance" http://serverperformance.blogspot.com
* @author (Basado en) http://java.sun.com
*/

public void close() throws IOException {
// Libera recursos del fichero temporal
if (usedTempFile) {
flushBufferToFile();
try {
//if (out!=null) no puede ser nulo después de flushBufferToFile()
out.close();
out = null;
}
catch (Throwable ignored) {}
try {
if (in!=null)
in.close();
in = null;
}
catch (Throwable ignored) {}
if (deleteTempFileOnClose) {
try {
// Borra el fichero temporal.
// Aunque está marcado para ser borrado al cerrar la JVM,
// por seguridad y optimización de recursos, lo hago ya

tempFile.delete();
}
catch (Throwable ignored) {}
}
if (deleteTempFileOnClose) {
try {
// Borra el fichero temporal.
// Aunque está marcado para ser borrado al cerrar la JVM,
// por seguridad y optimización de recursos, lo hago ya

tempFile.delete();
}
catch (Throwable ignored) {}
}
}
// Libera recursos en memoria
buf = null;
}

/** Returns the total buffer size

* @author "Server Performance" http://serverperformance.blogspot.com
* @author (Basado en) http://java.sun.com
*/

public int size() throws IOException {
if (usedTempFile) {
flushBufferToFile();
return (int)tempFile.length();
}
else {
return count;
}
}

/**
* @author "Server Performance" http://serverperformance.blogspot.com
*/

public void closeAndDelete() throws IOException {
boolean oldFlag = deleteTempFileOnClose;
deleteTempFileOnClose = true;
close();
}

/**
* @author "Server Performance" http://serverperformance.blogspot.com
*/

public void finalize() {
// Por si acaso...
(en las clases de java.io también lo hace...)

try {
flush();
close();
}
catch (Throwable ignored) {}
}

}

En cualquier caso, como muchas otras encapsulaciones, es algo que (al menos a mi) me parece una gran idea pero que en una Organización suele terminar no utilizándose por desconocimiento o por complejidad, o porque es un conocimiento que se pierde, o porque estaba mal documentado, o porque los procedimientos internos de comunicación eran más bien pobres hace unos años.

Es decir, es un código ideal para "núcleos" y componentes muy concretos, y quizás poco más.

En mi anterior vida, desde 2003 hasta hace unos meses, creo que este código se usado sólo en tres situaciones y me he quitado la espinita de encima porque en mi actual vida un invento de encapsulamiento muy similar a este nos ha salvado la vida en un caso muy concreto de invocaciones a módulos desacoplados donde podían darse situaciones de lo más diversa.

:-)

jueves, 10 de julio de 2008

Java SE 6u6p Performance Release

[Notas de octubre'08 y noviembre'08 ==>

Querido googler: si llegas a esta página -como muchos- buscando información de las performance releases del JRE 6, quizás te interesen los artículos posteriores Java SE 6u6-p Performance Release - YA DISPONIBLE EN OTRAS PLATAFORMAS, 6u6-p: Pues en Linux x64 peta!!!!!!! y Identificación de bugs en la versión 6u6p y workaround ]


Nueva actualización de rendimiento en Java 6. Continuando con lo comentado en la entrada Java SE 6u5p Performance Release, en julio Sun ha liberado ya la siguiente vuelta de tuerca: "JDK 6 Update 6 Performance Release" para plataformas SPARC. Del blog de David Dagastine:
I'm please to announce the release of JDK 6 Update 6 Performance Release on SPARC platforms. This is the latest of our performance releases and is the culmination of our optimization efforts over the last year. With this JDK Sun achieved many world records on SPECjappserver2004, SPECweb2005, SPECjbb2005, and the first ever SPECjvm2008 submission.
En breve estará disponible también para x64, aunque todavía no. Entre otras novedades, han mejorado el rendimiento en la clase TreeMap gracias a la aportación del equipo de Apache Harmony.

Referencias:

lunes, 7 de julio de 2008

Daytona Terabyte: Yahoo! gana la competición de rendimiento 2008 utilizando Apache Hadoop

No comment, aunque debería añadirlo también al post sobre mitos caídos...:

P.D: Como esto del "Java vs C++" es como lo del Madrid y el Barça (como tantas otras cosas, ver el artículo Sun Hotspot -vs- Bea JRockit), es decir que no puedes cambiar de opinión, se lo pasé a uno que me sé yo y su respuesta ha sido darles mérito a los ingenieros y programadores ("Tiene merito la cosa, han optimizado los algoritmos incluso para ganar desarrollándolo en java..."). Interesante reflexión :-)


jueves, 3 de julio de 2008

"Double checked locking" por fin funciona en Java

[Actualización noviembre 2008: referencia al final del post a un nuevo artículo de Jeremy "mascando" las variables volátiles en Java]

Es de todos conocido (espero) el pernicioso o impredecible efecto en Java del uso de la optimización "double-checked locking" tan usada en otros lenguajes como C++. El problema básicamente se da por dos motivos: en entornos multi-core / multi-procesador la JVM puede decidir que los threads utilicen copias locales de los punteros e incluso de algunas variables pequeñas para mejorar el rendimiento de acceso (las copias locales de la variable que tiene cada thread pueden no darse por enteradas del cambio global en condiciones de stress), aparte de que el compilador puede cambiar el orden de ejecución de algunas sentencias... :-(

Pero, desde J2SE 1.5, existe solución al problema añadiendo la palabra clave "volatile" como modificador de la variable que se utiliza como monitor, lo que asegura que todos los threads vean la misma versión de dicha variable, sin usar copias locales. Se penaliza muy levemente el rendimiento de acceso a esas variables dependiendo de la implementación de la JVM (pero mucha menos penalización que una región de exclusión mutua). Ahí está, el double-checked locking por fin funcionando en Java:



// Works with acquire/release semantics for volatile
// Broken under Java 1.4 and earlier semantics for volatile
class Foo {
  private volatile Helper helper = null;
  public Helper getHelper() {
    if (helper == null) {
      synchronized(this) {
        if (helper == null)
          helper = new Helper();
      }
    }
    return helper;
  }

  // other functions and members...
}

En estos tres artículos está perfectamente explicado así que no añadiré mucho más:

P.D: En este artículo se explica perfectamente el modificador volatile y su comparación con synchronized: http://www.javaperformancetuning.com/news/qotm030.shtml

(...) volatile only synchronizes the value of one variable between thread memory and "main" memory, whilesynchronized synchronizes the value of all variables between thread memory and "main" memory, and locks and releases a monitor to boot. Clearly synchronized is likely to have more overhead than volatile.

Y hago referencia a un nuevo artículo divulgativo de Jeremy Manson explicando muy mascadito qué implica el keyword volatile en Java: http://jeremymanson.blogspot.com/2008/11/what-volatile-means-in-java.html



ACTUALIZACIÓN: aunque este patrón sigue teniendo sentido en muchos escenarios, para implementar un patrón Singleton con lazy-initialization es mejor usar el idioma Initialization-on-demand holder. Para más info, véase el artículo