What does "StringBuilders are not thread safe" mean?
I have read several articles on the pros and cons String
and StringBuilder
in the Java programming language. In one of the articles, the author mentioned that:
StringBuilder is not thread safe , so use StringBuffer across multiple threads .
Unfortunately, I can't figure out what this means. Could you please explain the difference between String
, StringBuilder
and StringBuffer
especially in the context of "Thread Safety".
I would appreciate it if you could provide some sample code.
source to share
If multiple threads are modifying the same instance StringBuilder
, the result may be unexpected - i.e. some of the changes may be lost. This is why you should use StringBuffer in situations like this. If, however, each thread StringBuilder
can only be modified by one thread, it is better to use StringBuilder
it as it will be more efficient (thread safety is related to performance).
source to share
If multiple threads try to change the value of the StringBuilder object, the result will be strange. See the example below,
private StringBuilder sb = new StringBuilder("1=2");
public void addProperty(String name, String value) {
if (value != null && value.length() > 0) {
if (sb.length() > 0) {
sb.append(',');
}
sb.append(name).append('=').append(value);
}
}
If many threads call the addProperty method, the result will be strange (unpredictable).
Thread1: addProperty("a", "b");
Thread2: addProperty("c", "d");
Thread3: addProperty("e", "f");
Finally, when you call sb.toString (), the result is unpredictable. For example, it might output the result as 1=2,ac=d=b,e=f
, but your expectation would be1=2,a=b,c=d,e=f
source to share
A thread safety issue with c StringBuilder
is that the method call is StringBuilder
not synchronized.
Consider the implementation of the method StringBuilder.append(char)
:
public StringBuilder append(boolean b) {
super.append(b);
return this;
}
// from the superclass
public AbstractStringBuilder append(char c) {
int newCount = count + 1;
if (newCount > value.length)
expandCapacity(newCount);
value[count++] = c;
return this;
}
Now, suppose you have two threads sharing an instance StringBuilder
and both are trying to add a character at the same time. Suppose they both enter the operator at the same time value[count++] = c;
, and count
- 1
. Each of them will write their character to the buffer in value[1]
, and then update count
. Obviously, only one character can be stored there ... so the other will be lost. Also, one of the increments count
is likely to be lost.
Worse, the operator value[count++] = c;
can crash even if both threads don't arrive there at the same time. The reason is that the Java memory model says that if proper synchronization does not exist (the "occurs before" relationship), the second thread is not guaranteed to see the memory updates created by the first thread. What actually happens depends on whether and when the first stream updates are written to main memory.
Now let's look at StringBuffer.append(char)
:
public synchronized StringBuffer append(char c) {
super.append(c); // calls the "AbstractStringBuilder.append" method above.
return this;
}
Here we can see that the method append
is equal synchronized
. This means two things:
-
Two threads cannot simultaneously execute a superclass method
append
on the same objectStringBuffer
. Thus, the first scenario cannot happen. -
synchronize
means existshappens before
between successive calls madeStringBuffer.append
by different threads. This means that the last thread is guaranteed to see the updates made in the previous one.
The case String
is different again. If we look at the code, we can see that there is no explicit synchronization. But that's okay, because the object String
is actually immutable; those. there are no methods in the API String
that will cause an externally observable change in the state of the object String
. Besides:
-
The special behavior of instance variables
final
and constructors means that all threads will see the correct initial state for anyoneString
. -
In one place where
String
changed behind the scenes, the methodhashCode()
will work correctly regardless of whether the thread sees the last changes in the variablehash
.
Literature:
- Source code for StringBuilder - http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8-b132/java/lang/StringBuilder.java
- Source code for StringBuffer - http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8-b132/java/lang/StringBuffer.java
- Source code for String - http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8-b132/java/lang/String.java
source to share
Because StringBuilder is not synchronized whereas StringBuffer is synchronized. When using a StringBuilder in a multithreaded environment, multiple threads can simultaneously capture the StringBuilder object, and the output it produces cannot be predicted, so the StringBuilder is not thread safe ...
By using StringBuffer, we can overcome the thread safety issue where StringBuffer is thread safety because it synchronizes when only one thread can access at a time, so the output it produces can be expected and predictable.
source to share