10

Вопрос, который я задаю, относится к 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.

+0

Странно, что на моей машине печатаются «199» и «200», а размер «StringBuilder» совпадает с количеством символов. Я запускал это несколько раз, но каждый раз получая те же результаты –

+0

Это также имеет отношение к среде IDE. Первоначально я использовал IntelliJ, и он всегда давал длины '199' и' 200'. Затем я запустил это в BlueJ, и он дал переменную длину при каждом прогоне. Кажется, что IntelliJ лучше справляется с несколькими потоками, чем другие IDE. –

+0

любой шанс получить результаты из командной строки IDE? :) –

ответ

7

Поведение класса non-threadsafe при одновременном доступе несколькими потоками по определению «undefined».

Любая попытка установить детерминированное поведение в таком случае - IMHO, - просто огромная трата времени.

+3

Тем не менее, я думаю, что этот случай с напечатанной длиной 137 и реальной длиной 50 невозможно даже с «неопределенным» поведением. –

0

Несоответствие между количеством напечатанных символов и напечатанной длиной происходит от печати значений, пока другой поток все еще работает. Ошибка связана с синхронизацией и вызвана обоими потоками, которые пытаются одновременно изменить один и тот же объект.

Между первым и вторым println другой поток завершил дополнительный цикл и изменил содержимое буфера.

Смежные вопросы