Java StringBuffer
Usa la clase StringBuffer con seguridad entre hilos en Java para cadenas mutables compartidas entre múltiples hilos.
StringBuffer es el hermano mayor de StringBuilder. Comparten una API, comparten un padre (AbstractStringBuilder) y comparten una implementación de buffer de bytes. La única diferencia es que los métodos de StringBuffer son synchronized: cada append, insert, delete y toString adquiere un monitor sobre el buffer durante la duración de la llamada. Esto hace que un StringBuffer sea seguro para compartir entre hilos. También hace que cada operación sea más lenta que la equivalente en StringBuilder.
La biblioteca de clases original de Java solo incluía StringBuffer. StringBuilder llegó en JDK 1.5 (2004) precisamente porque la sobrecarga de sincronización era un problema en el caso común de un único hilo. Durante las últimas dos décadas, StringBuilder ha sido la opción predeterminada y StringBuffer la elección de nicho.
Cuándo usarlo realmente
El resumen honesto primero: casi nunca. La lista de casos de uso reales es corta, y la mayoría tiene mejores respuestas en Java moderno.
Un StringBuffer es la elección correcta solo cuando todos estos puntos son verdaderos:
- Un buffer es genuinamente compartido entre múltiples hilos.
- Cada hilo añade o inserta de forma independiente en él.
- Una
Stringinmutable final es lo que el consumidor necesita al final. - No puedes reestructurar fácilmente el código para que cada hilo construya su propio
StringBuildery un coordinador los una.
En la mayoría del código concurrente, el último punto es la salida: dale a cada hilo su propio StringBuilder, devuelve las cadenas y concatena al final. Eso evita la contención en un único monitor y elimina la sobrecarga de sincronización del camino crítico.
El caso restante — un pequeño número de hilos trazando en un buffer de diagnóstico compartido, un registro de auditoría al que añaden varios actores — es donde StringBuffer todavía se gana su lugar.
La API refleja StringBuilder
Cada mutador de StringBuilder existe en StringBuffer con la misma firma y el mismo tipo de retorno. Dado que ambas clases extienden AbstractStringBuilder, son gemelas en comportamiento; la única diferencia es el bloqueo:
StringBuffer sb = new StringBuffer(64);
sb.append("Hello, ")
.append("world")
.insert(0, "[INFO] ")
.append('!');
String out = sb.toString();Constructores, length(), capacity(), charAt, substring, indexOf, reverse, delete, replace, setLength, ensureCapacity, trimToSize — todos presentes, todos devuelven los mismos tipos. La revisión de código para StringBuffer es esencialmente la misma que para StringBuilder, más una nota sobre qué monitor se mantiene.
Qué significa sincronización aquí
synchronized en métodos de instancia bloquea sobre el propio objeto buffer. Por lo tanto:
StringBuffer log = new StringBuffer();
// Thread A
log.append("hit /users\n");
// Thread B (concurrently)
log.append("hit /orders\n");Cada append se ejecuta atómicamente con respecto al otro — ningún hilo romperá los bytes del otro. El resultado será "hit /users\nhit /orders\n" o "hit /orders\nhit /users\n". No será "hit /uhit /ordserss\n\n". Esa es la garantía.
La garantía no se extiende entre llamadas a métodos. Una secuencia de dos appends no es atómica:
log.append(level); // unlock
// ← another thread might append here
log.append(": ");
log.append(message);
log.append('\n');Un segundo hilo puede insertar una escritura entre los dos primeros appends e intercalarse. Si quieres que un registro completo llegue de forma contigua, haz el ensamblaje en un StringBuilder local al hilo primero y luego añade la cadena terminada de una vez al StringBuffer compartido. O — de forma equivalente — envuelve la escritura de varios pasos en un bloque synchronized (log) { ... } explícito.
Rendimiento, brevemente
Cada llamada bloqueada paga por el monitor: en HotSpot moderno eso es barato en el camino rápido de bloqueo sesgado cuando no hay contención, y notablemente más costoso bajo contención. Comparado con StringBuilder, un solo append sin contención es como mucho un pequeño factor constante más lento. Bajo contención es dramáticamente más lento, porque los hilos se bloquean esperando el monitor.
La conclusión: la contención es el coste, no la primitiva de bloqueo en sí. Si has medido un StringBuffer con mucha contención, la solución correcta es reestructurar para que cada hilo construya su propia parte — no seguir ajustando el buffer.
Un ejemplo trabajado
El programa siguiente inicia un pequeño grupo de hilos escritores que comparten un StringBuffer y añaden líneas de marcador. Los métodos sincronizados mantienen cada línea intacta; la escritura deliberada en dos pasos demuestra por qué a veces todavía necesitas un bloque synchronized externo para mantener juntas un grupo de escrituras.
Observa la salida de la ejecución y verás dos patrones. Los marcadores [T?:i] están individualmente intactos — sin etiquetas rotas — porque cada uno es una única llamada a append. Pero el orden en que aparecen los marcadores de diferentes hilos está intercalado. Las tres líneas -- end of T? --, por el contrario, cada una llega de forma contigua, porque el bloque synchronized (shared) { ... } externo mantiene el bloqueo a través de los cuatro appends para ese grupo.
Qué sigue
Eso cubre el ensamblaje de cadenas mutables en ambas variantes. Producir cadenas para mostrar es la próxima preocupación: renderizar números con un ancho fijo, formatear fechas, rellenar columnas. Continúa con formateo de cadenas en Java.