введение
Я пошел на собеседование в пятницу, и на собеседовании мне задали глупый вопрос.
Интервьюер: StringBuilder и StringBuffer в чем разница?
я: StringBuilder не является потокобезопасным, StringBuffer является потокобезопасным
Интервьюер: Где небезопасная точка StringBuilder?
Я:. . . (тупой)
До этого я только помнил, что StringBuilder не является потокобезопасным, а StringBuffer — потокобезопасным, а почему StringBuilder небезопасен, я никогда об этом не задумывался.
анализировать
Прежде чем анализировать и ставить задачу, нам нужно знать, что внутренняя реализация StringBuilder и StringBuffer такая же, как и у класса String.Они хранят строки через массив символов.Разница в том, что массив символов в классе String является окончательным изменяемые и неизменяемые Массивы символов StringBuilder и StringBuffer могут изменяться.
Во-первых, давайте посмотрим на кусок кода, чтобы увидеть, какие проблемы будут возникнуть в многопоточных операциях на объектах StringBuilder.
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());
}
}
Мы видим, что этот код создает 10 потоков, каждый из которых цикл 1000 раз, чтобы добавить символы в объект StringBuilder. При нормальных обстоятельствах код должен выводить 10000, но что будет фактический выходной выход?
Мы видели, что «9326» выводится, меньше ожидаемого 10000, а также выдает исключение ArrayindexoutOfBoundSexception (исключение не является списком).
1. Почему выходное значение отличается от ожидаемого?
Давайте взглянем на две переменные-члены StringBuilder (эти две переменные-члены фактически определены в AbstractStringBuilder, и StringBuilder, и StringBuffer наследуют AbstractStringBuilder)
//存储字符串的具体内容
char[] value;
//已经使用的字符数组的数量
int count;
Посмотрите еще раз на метод append() StringBuilder:
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
Метод append() родительского класса AbstractStringBuilder, вызываемый методом append() класса StringBuilder.
1.public AbstractStringBuilder append(String str) {
2. if (str == null)
3. return appendNull();
4. int len = str.length();
5. ensureCapacityInternal(count + len);
6. str.getChars(0, len, value, count);
7. count += len;
8. return this;
9.}
Независимо от того, какова пятая строка кода и шестая строка кода, давайте посмотрим на седьмой линии напрямую, Count + = Len не является атомной операцией. Предположим, что значение Count равно 1, значение Len равно 1, два потока выполняются одновременно, значение имени подсчета имеет значение 10, и результат назначен подсчет после завершения, поэтому два потока выполнено. Значение подсчета 11, а не 12. Вот почему значение вывода тестового кода составляет более 10000 причин.
2. Почему возникает исключение ArrayIndexOutOfBoundsException.
Давайте обратно на пятую строку исходного кода метода Append () ABSEDSTRINGBUILDER. Метод EnsureCapityInternal () - проверять, может ли мощность исходного символа CHAR из объекта StringBuilder удерживайте новую строку. Расширение.
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
Логика расширения заключается в создании нового массива символов.Емкость нового массива символов в два раза больше исходного массива символов плюс 2. Затем содержимое исходного массива копируется в новый массив через System.arryCopy( ) и, наконец, указатель указывает на новый массив new char array.
void expandCapacity(int minimumCapacity) {
//计算新的容量
int newCapacity = value.length * 2 + 2;
//中间省略了一些检查逻辑
...
value = Arrays.copyOf(value, newCapacity);
}
Метод Arrays.copyOf()
public static char[] copyOf(char[] original, int newLength) {
char[] copy = new char[newLength];
//拷贝数组
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
Шестая строка исходного кода метода append() класса AbstractStringBuilder предназначена для копирования содержимого массива char объекта String в массив char объекта StringBuilder Код выглядит следующим образом:
str.getChars(0, len, value, count);
Метод getChars()
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
//中间省略了一些检查
...
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
Процесс копирования показан на рисунке ниже
Когда поток 1 продолжает выполнять метод str.getChars() в шестой строке, полученное значение счетчика равно 6, и при выполнении копии массива символов выдается исключение ArrayIndexOutOfBoundsException.
До сих пор было проанализировано, почему StringBuilder небезопасен. Что, если мы заменим объект StringBuilder тестового кода на объект StringBuffer?
Итак, какие средства использует StringBuffer для обеспечения безопасности потоков? Вы узнаете об этой проблеме, нажав на метод append() StringBuffer.