Вопрос, который я задаю, относится к Difference between StringBuilder and StringBuffer, но не то же самое. Я хочу посмотреть, что на самом деле происходит, если StringBuilder модифицируется двумя потоками одновременно.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 т.е.
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 Кодеры - Я просто сделал еще один тестовый прогон:
2: ababbabababaabbababaabbababaababaabbaababbaaababbaabbabbabbabababbabababbbabbbbbabababbaabababbabaabaaabaababbaabaababababbaabbbabbbbbababababbababaab
1: ababbabababaabbababaabbababaababaabbaababbaaababbaabbabbabbabababbabababbbabbbbbabababbaabababbabaabaaabaababbaabaababababbaabbbabbbbbababababbababaab
1 length: 150
2 length: 150
Java версия: 1.6.0_45 и я использую затмение версии: Eclipse, Java EE IDE для веб-разработчиков. Версия: Juno Service Release 2 Сложение ID: 20130225-0426
UPDATE 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
, когда он расширяет размер. Но по-прежнему меня беспокоит, как происходит запись, прежде чем размер будет расширен, независимо от порядка выполнения.
BTW, я склонен согласиться с ответом @ GreyBeardedGeek, что все это упражнение представляет собой огромную трату времени :-). Иногда мы видим только симптомы, то есть вывод какого-то кода, и задаемся вопросом, что происходит не так.Этот вопрос объявил a priori, что два потока изменяют (очень известный) поток небезопасного объекта.
ОБНОВЛЕНИЕ 3: Вот официальный ответ от Java Concurrency in Practice p. 35:
В отсутствии синхронизации, компилятора, процессора и выполнения может сделать некоторые совершенно странные вещи в порядок, в котором операций появляются выполнить. Попытки рассуждать о порядке в , что действия памяти «должны» произойти в недостаточно синхронизированном многопоточных программах почти наверняка будут неверными.
Рассуждение о недостаточно синхронизированных параллельных программах непомерно сложно.
Существует также хороший пример NoVisibility
в книге на с. 34.
Странно, что на моей машине печатаются «199» и «200», а размер «StringBuilder» совпадает с количеством символов. Я запускал это несколько раз, но каждый раз получая те же результаты –
Это также имеет отношение к среде IDE. Первоначально я использовал IntelliJ, и он всегда давал длины '199' и' 200'. Затем я запустил это в BlueJ, и он дал переменную длину при каждом прогоне. Кажется, что IntelliJ лучше справляется с несколькими потоками, чем другие IDE. –
любой шанс получить результаты из командной строки IDE? :) –