Java StringBuffer
Use a classe thread-safe StringBuffer em Java para strings mutáveis compartilhadas entre threads.
StringBuffer é o irmão mais velho de StringBuilder. Eles compartilham uma API, compartilham um pai (AbstractStringBuilder) e compartilham uma implementação de buffer de bytes. A única diferença é que os métodos de StringBuffer são synchronized — cada append, insert, delete e toString adquire um monitor no buffer durante a duração da chamada. Isso torna o StringBuffer seguro para compartilhar entre threads. Também torna cada operação mais lenta do que o equivalente em StringBuilder.
A biblioteca de classes Java original incluía apenas StringBuffer. O StringBuilder chegou no JDK 1.5 (2004) precisamente porque a sobrecarga de sincronização era um problema no caso comum de thread único. Nas últimas duas décadas, StringBuilder tem sido a escolha padrão e StringBuffer a escolha de nicho.
Quando realmente usá-lo
O resumo honesto primeiro: quase nunca. A lista de casos de uso reais é curta, e a maioria deles tem respostas melhores no Java moderno.
Um StringBuffer é a escolha certa apenas quando cada um destes é verdadeiro:
- Um buffer é genuinamente compartilhado entre múltiplas threads.
- Cada thread acrescenta ou insere nele de forma independente.
- Uma
Stringimutável final e única é o que o consumidor precisa no final. - Você não pode facilmente reestruturar o código para que cada thread construa seu próprio
StringBuildere um coordenador os una.
Na maioria dos códigos concorrentes, o último ponto é a saída: dê a cada thread seu próprio StringBuilder, retorne as strings e concatene no final. Isso evita contenção em um único monitor e remove a sobrecarga de sincronização do caminho crítico.
O caso restante — um pequeno número de threads rastreando em um buffer de diagnóstico compartilhado, um log de auditoria acrescentado por vários atores — é onde o StringBuffer ainda justifica sua existência.
A API espelha o StringBuilder
Cada mutador em StringBuilder existe em StringBuffer com a mesma assinatura e o mesmo tipo de retorno. Como ambas as classes estendem AbstractStringBuilder, são gêmeos comportamentais; a única diferença é o bloqueio:
StringBuffer sb = new StringBuffer(64);
sb.append("Hello, ")
.append("world")
.insert(0, "[INFO] ")
.append('!');
String out = sb.toString();Construtores, length(), capacity(), charAt, substring, indexOf, reverse, delete, replace, setLength, ensureCapacity, trimToSize — todos presentes, todos retornam os mesmos tipos. A revisão de código para StringBuffer é essencialmente a mesma que para StringBuilder, mais uma nota sobre qual monitor está sendo mantido.
O que a sincronização significa aqui
synchronized em métodos de instância bloqueia no próprio objeto do buffer. Portanto:
StringBuffer log = new StringBuffer();
// Thread A
log.append("hit /users\n");
// Thread B (concurrently)
log.append("hit /orders\n");Cada append é executado atomicamente em relação ao outro — nenhuma thread vai corromper os bytes da outra. O resultado será "hit /users\nhit /orders\n" ou "hit /orders\nhit /users\n". Não será "hit /uhit /ordserss\n\n". Essa é a garantia.
A garantia não se estende entre chamadas de método. Uma sequência de dois appends não é atômica:
log.append(level); // unlock
// ← another thread might append here
log.append(": ");
log.append(message);
log.append('\n');Uma segunda thread pode inserir uma escrita entre os dois primeiros appends e intercalar. Se você quiser que um registro inteiro seja gravado de forma contígua, faça a montagem em um StringBuilder local da thread primeiro e depois append a string finalizada uma única vez no StringBuffer compartilhado. Ou — equivalentemente — envolva a escrita de várias etapas em um bloco explícito synchronized (log) { ... }.
Desempenho, brevemente
Cada chamada bloqueada paga pelo monitor: no HotSpot moderno, isso é barato pelo caminho rápido de bloqueio com viés quando não há contenção, e notavelmente mais caro sob contenção. Comparado ao StringBuilder, um único append sem contenção é no máximo um pequeno fator constante mais lento. Sob contenção é dramaticamente mais lento, porque as threads ficam bloqueadas aguardando o monitor.
A conclusão: a contenção é o custo, não a primitiva de bloqueio em si. Se você mediu um StringBuffer quente e com contenção, a correção certa é reestruturar para que cada thread construa seu próprio trecho — não ajustar ainda mais o buffer.
Um exemplo prático
O programa abaixo inicia um pequeno pool de threads escritoras que compartilham um StringBuffer e acrescentam linhas de marcação. Os métodos sincronizados mantêm cada linha intacta; a escrita deliberada em duas etapas demonstra por que às vezes ainda é necessário um bloco synchronized externo para manter um grupo de escritas juntas.
Observe a saída da execução e você verá dois padrões. Os marcadores [T?:i] estão individualmente intactos — sem tags corrompidas — porque cada um é uma única chamada append. Mas a ordem em que os marcadores de threads diferentes aparecem é intercalada. As três linhas -- end of T? --, por outro lado, cada uma fica contígua, porque o bloco synchronized (shared) { ... } externo mantém o bloqueio em todos os quatro appends daquele grupo.
O que vem a seguir
Isso cobre a montagem de strings mutáveis nas duas variantes. Produzir strings para exibição é a próxima preocupação: renderizar números com largura fixa, formatar datas, preencher colunas. Continue em Formatação de strings Java.