Why StringBuilder Is Not Thread‑Safe and How It Differs from StringBuffer
The article explains why StringBuilder is not thread‑safe, analyzes its internal implementation and the non‑atomic count update that leads to incorrect lengths and occasional ArrayIndexOutOfBoundsException, and shows that using synchronized StringBuffer avoids these issues.
During a Java interview the candidate was asked to compare StringBuilder and StringBuffer . The obvious answer is that StringBuilder is not thread‑safe while StringBuffer is synchronized, but the deeper reason for the lack of safety is often overlooked.
Both classes inherit from AbstractStringBuilder , which stores the characters in a mutable char[] value and tracks the used length with an int count . The append method of StringBuilder simply delegates to the parent class, where the critical operation is:
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;The update of count is not atomic. When multiple threads execute the method concurrently, they can read the same original count value, add len , and write back the same result, causing the final length to be smaller than the expected total (e.g., 9326 instead of 10000). This race condition also makes the occasional ArrayIndexOutOfBoundsException possible when the capacity‑expansion logic runs concurrently.
The capacity expansion performed by ensureCapacityInternal checks whether the current array can accommodate the new characters. If not, it creates a new array with roughly double the size plus two, copies the old contents with System.arraycopy , and replaces the reference. If one thread finishes the expansion and another thread still uses the old count value, the subsequent getChars call may write beyond the new array bounds, triggering the exception.
Because StringBuffer synchronizes the entire append method, the above race conditions cannot occur, and the same test program reliably prints 10000 .
Below is the demonstration code used in the analysis:
public class StringBuilderDemo {
public static void main(String[] args) throws InterruptedException {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 1000; j++) {
stringBuilder.append("a");
}
}
}).start();
}
Thread.sleep(100);
System.out.println(stringBuilder.length());
}
}In summary, StringBuilder is unsafe in multithreaded contexts because its internal state updates are not atomic, and its capacity‑expansion logic can cause index errors when accessed concurrently. Switching to the synchronized StringBuffer eliminates these problems.
Architect's Tech Stack
Java backend, microservices, distributed systems, containerized programming, and more.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.