W3docs

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 String imutá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 StringBuilder e 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.

java— editable, runs on the server

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.

Prática

Prática
Qual afirmação descreve com mais precisão a diferença entre `StringBuffer` e `StringBuilder`?
Qual afirmação descreve com mais precisão a diferença entre `StringBuffer` e `StringBuilder`?
Was this page helpful?