Вопрос, который я задаю, связан с Разница между StringBuilder и StringBuffer, но не то же самое. Я хочу посмотреть, что на самом деле происходит, если StringBuilder изменен двумя потоками одновременно.
Я написал следующие классы:
public class ThreadTester
{
public static void main(String[] args) throws InterruptedException
{
Runnable threadJob = new MyRunnable();
Thread myThread = new Thread(threadJob);
myThread.start();
for (int i = 0; i < 100; i++)
{
Thread.sleep(10);
StringContainer.addToSb("a");
}
System.out.println("1: " + StringContainer.getSb());
System.out.println("1 length: " + StringContainer.getSb().length());
}
}
public class MyRunnable implements Runnable
{
@Override
public void run()
{
for (int i = 0; i < 100; i++)
{
try
{
Thread.sleep(10);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
StringContainer.addToSb("b");
}
System.out.println("2: " + StringContainer.getSb());
System.out.println("2 length: " + StringContainer.getSb().length());
}
}
public class StringContainer
{
private static final StringBuffer sb = new StringBuffer();
public static StringBuffer getSb()
{
return sb;
}
public static void addToSb(String s)
{
sb.append(s);
}
}
Сначала я сохранил StringBuffer в StringContainer. Поскольку StringBuffer является потокобезопасным, за один раз к нему может присоединяться только один поток, поэтому вывод согласован - оба потока сообщают о длине буфера как 200, например:
1: abababababababababbaabababababababbaababababababababababababbabaabbababaabbaababababbababaabbababaabababbaabababbababababaababababababababbababaabbaababbaababababababbaababbababaababbabaabbababababaab
1 length: 200
2: abababababababababbaabababababababbaababababababababababababbabaabbababaabbaababababbababaabbababaabababbaabababbababababaababababababababbababaabbaababbaababababababbaababbababaababbabaabbababababaab
2 length: 200
или один из них сообщил 199, а другой 200, например:
2: abbabababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab
2 length: 199
1: abbababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababa
1 length: 200
Ключ состоит в том, что последний поток для завершения составляет 200.
Теперь я изменил StringContainer, чтобы иметь StringBuilder вместо StringBuffer i.e.
public class StringContainer
{
private static final StringBuilder sb = new StringBuilder();
public static StringBuilder getSb()
{
return sb;
}
public static void addToSb(String s)
{
sb.append(s);
}
}
Я ожидаю, что некоторые из записей будут переписаны, что происходит. Но содержимое StringBuilder и длины иногда не совпадают:
1: ababbabababaababbaabbabababababaab
1 length: 137
2: ababbabababaababbaabbabababababaab
2 length: 137
Как вы можете видеть, печатный контент имеет только 34 символа, но длина равна 137. Почему это происходит?
@Extreme Coders - я сделал еще один тестовый прогон:
2: ababbabababaabbababaabbababaababaabbaababbaaababbaabbabbabbabababbabababbbabbbbbabababbaabababbabaabaaabaababbaabaababababbaabbbabbbbbababababbababaab
1: ababbabababaabbababaabbababaababaabbaababbaaababbaabbabbabbabababbabababbbabbbbbabababbaabababbabaabaaabaababbaabaababababbaabbbabbbbbababababbababaab
1 length: 150
2 length: 150
Версия Java: 1.6.0_45, и я использую версию eclipse: Eclipse Java EE IDE для веб-разработчиков. Версия: Juno Service Release 2 Идентификатор сборки: 20130225-0426
ОБНОВЛЕНИЕ 1: Я запустил это внешнее затмение, и теперь они кажутся подходящими, но иногда я получаю исключение ArrayIndexOutOfBoundsException:
$ java -version
java version "1.6.0_27"
OpenJDK Runtime Environment (IcedTea6 1.12.5) (6b27-1.12.5-0ubuntu0.12.04.1)
OpenJDK Server VM (build 20.0-b12, mixed mode)
$ java ThreadTester
1: ababbbbbabbabababababaababbaabbbaabababbbababbabababbabbababbbbbbabaabaababbbbbbabbbbbaabbaaabbbbaabbbababababbbbabbababab
1 length: 123
2: ababbbbbabbabababababaababbaabbbaabababbbababbabababbabbababbbbbbabaabaababbbbbbabbbbbaabbaaabbbbaabbbababababbbbabbababab
2 length: 123
$ java ThreadTester
2: abbabaabbbbbbbbbababbbbbabbbabbbabaaabbbbbbbabababbbbbbbbbabbbbbbbababababbabbbbaabbbaaabbabaaababaaaabaabbaabbbb
2 length: 115
1: abbabaabbbbbbbbbababbbbbabbbabbbabaaabbbbbbbabababbbbbbbbbabbbbbbbababababbabbbbaabbbaaabbabaaababaaaabaabbaabbbb
1 length: 115
$ java ThreadTester
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
at java.lang.System.arraycopy(Native Method)
at java.lang.String.getChars(String.java:862)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:408)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at StringContainer.addToSb(StringContainer.java:14)
at ThreadTester.main(ThreadTester.java:14)
2: abbbbbbababbbbabbbbababbbbaabbabbbaaabbbababbbbabaabaabaabaaabababaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
2 length: 114
ArrayIndexOutOfBoundsException также происходит при запуске из eclipse.
ОБНОВЛЕНИЕ 2: Возникают две проблемы. Первая проблема с содержимым StringBuilder, не соответствующая длине, происходит только в Eclipse, а не при запуске в командной строке (по крайней мере, в 100 раз я запускал ее в командной строке, она никогда не происходила).
Вторая проблема с ArrayIndexOutOfBoundsException должна быть связана с внутренней реализацией класса StringBuilder, который хранит массив символов и делает Arrays.copyOf
, когда он расширяет размер. Но по-прежнему меня беспокоит, как происходит запись, прежде чем размер будет расширен, независимо от порядка выполнения.
Кстати, я склонен согласиться с @GreyBeardedGeek ответить, что все это упражнение - огромная трата времени:-). Иногда мы видим только симптомы, то есть вывод какого-то кода, и задаемся вопросом, что происходит не так. Этот вопрос объявил a priori, что два потока изменяют (очень известный) поток небезопасного объекта.
ОБНОВЛЕНИЕ 3: Вот официальный ответ от Java Concurrency на практике. 35:
-
В отсутствие синхронизации компилятор, процессор и время выполнения может сделать некоторые совершенно странные вещи в порядке, в котором операции выполняются. Попытки обосновать порядок в какие действия памяти "должны" произойти в недостаточно синхронизированной многопоточные программы почти наверняка будут неверными.
-
Рассуждение о недостаточно синхронизированных параллельных программах непомерно трудно.
Существует также хороший пример NoVisibility
в книге на стр. 34.