Author: qianshan mountain juejin. Im/post / 5 d6228046fb9a06add4e37fe
The introduction
Interviewer: What’s the difference between StringBuilder and StringBuffer?
Me: StringBuilder is not thread-safe, StringBuffer is thread-safe
Interviewer: So what’s unsafe about StringBuilder?
Me:… (Mute)
StringBuilder is not thread-safe. StringBuffer is thread-safe. Why StringBuilder is not thread-safe has never occurred to me.
Analysis of the
Before we look at this, we need to know that the internal implementation of StringBuilder and StringBuffer, like String, stores strings ina char array, except that the char array in String is final and immutable. The char arrays of StringBuilder and StringBuffer are mutable.
Let’s start with a snippet of code to see what goes wrong with multithreading a StringBuilder object. 3 differences between StringBuffer and StringBuilder! I want to read this one as well.
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()); }}Copy the code
We can see that this code creates 10 threads, each looped 1000 times into the StringBuilder object for the Append character.
Normally the code should output 10000, but what does the actual run output?
We see the output of the “9326”, 10000 less than expected, and it also throws a ArrayIndexOutOfBoundsException anomalies (abnormal not will now).
1. Why is the output value different from the expected value
Let’s take a look at two member variables of StringBuilder that are actually defined in AbstractStringBuilder. Both StringBuilder and StringBuffer inherit AbstractStringBuilder.
// Store the contents of the string char\[\] value; // The number of character arrays already used int count;Copy the code
Look again at the StringBuilder append() method:
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}Copy the code
AbstractStringBuilder’s append() method calls AbstractStringBuilder’s append() method
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}Copy the code
Regardless of what’s going on in line 5 and 6, let’s look at line 7. Count += len is not an atomic operation.
Let’s say that count is 10, len is 1, and both threads go to line 7, and they both get count 10, and then they add and assign the result to count, so they get count 11 instead of 12. This is why the test code outputs a value less than 10000.
2, why sell ArrayIndexOutOfBoundsException anomalies.
AbstractStringBuilder append(), ensureCapacityInternal() checks whether the char array of a StringBuilder object can hold a new string. Call expandCapacity() to expand the char array.
private void ensureCapacityInternal(int minimumCapacity) { // overflow-conscious code if (minimumCapacity - value.length > 0) expandCapacity(minimumCapacity); }Copy the code
The expansion logic is to create a new char array with twice the size of the original char array plus 2, copy the contents of the original char array to the new array using system.arrycopy (), and finally point to the new char array.
Void expandCapacity(int minimumCapacity) {// calculate newCapacity int newCapacity = value.length * 2 + 2; // Omit some checking logic... value = Arrays.copyOf(value, newCapacity); }Copy the code
Arrys. CopyOf () method
public static char\[\] copyOf(char\[\] original, int newLength) { char\[\] copy = new char\[newLength\]; Arraycopy (original, 0, copy, 0, math.min (original. Length, newLength)); return copy; }Copy the code
AbstractStringBuilder append(); AbstractStringBuilder append();
str.getChars(0, len, value, count);Copy the code
GetChars () method
Public void cend (int srcBegin, int srcEnd, char DST \[\], int dstBegin) {public void cend (int srcBegin, int srcEnd, char DST \[\], int dstBegin) { System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin); }Copy the code
See the following figure for the copy process
Suppose we now have two threads executing append() on StringBuilder at the same time, and both threads have completed ensureCapacityInternal() on line 5, and count=5.
At this point thread 1 runs out of CPU time slice, thread 2 continues execution. Count becomes 6 after thread 2 executes the entire append() method.
Thread 1 STR., continue to implement the sixth line getChars () method to get the count value is 6, enforce char array copy will be thrown ArrayIndexOutOfBoundsException anomalies.
At this point, the analysis of why StringBuilder is unsafe is done. What if we replaced the StringBuilder object of our test code with a StringBuffer object?
* Output 10000 of course! *
So what does StringBuffer do to make it thread-safe? You can see this in the append() method of StringBuffer.
Read more on my blog:
1.Java JVM, Collections, Multithreading, new features series tutorials
2.Spring MVC, Spring Boot, Spring Cloud series tutorials
3.Maven, Git, Eclipse, Intellij IDEA series tools tutorial
4.Java, backend, architecture, Alibaba and other big factory latest interview questions
Life is good. See you tomorrow