10

La domanda che sto chiedendo è relativa a Difference between StringBuilder and StringBuffer ma non uguale. Voglio vedere cosa succede realmente se uno StringBuilder viene modificato da due thread contemporaneamente.StringBuilder modificato da più thread

ho scritto le seguenti classi:

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); 
    } 
} 

Inizialmente ho tenuto uno StringBuffer nel StringContainer. Poiché StringBuffer è thread-safe, alla volta, solo un thread possibile aggiungere ad esso, quindi l'uscita è coerente - o entrambi i fili riportata la lunghezza del buffer come 200, come:

1: abababababababababbaabababababababbaababababababababababababbabaabbababaabbaababababbababaabbababaabababbaabababbababababaababababababababbababaabbaababbaababababababbaababbababaababbabaabbababababaab 
1 length: 200 
2: abababababababababbaabababababababbaababababababababababababbabaabbababaabbaababababbababaabbababaabababbaabababbababababaababababababababbababaabbaababbaababababababbaababbababaababbabaabbababababaab 
2 length: 200 

o uno di essi segnalati 199 e l'altro 200, come:

2: abbabababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab 
2 length: 199 
1: abbababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababa 
1 length: 200 

la chiave è che l'ultimo filo per completare i rapporti una lunghezza di 200.

Ora, ho cambiato StringContainer avere uno StringBuilder invece di StringBuffer cioè

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); 
    } 
} 

Mi aspetto che alcune delle scritture vengano sovrascritte, il che sta accadendo. Ma il contenuto del StringBuilder e le lunghezze non corrispondono a volte:

1: ababbabababaababbaabbabababababaab 
1 length: 137 
2: ababbabababaababbaabbabababababaab 
2 length: 137 

Come si può vedere il contenuto stampato ha solo 34 caratteri, ma la lunghezza è 137. Perché succede questo?

@Extreme Coders - ho appena fatto un altro test di corsa:

2: ababbabababaabbababaabbababaababaabbaababbaaababbaabbabbabbabababbabababbbabbbbbabababbaabababbabaabaaabaababbaabaababababbaabbbabbbbbababababbababaab 
1: ababbabababaabbababaabbababaababaabbaababbaaababbaabbabbabbabababbabababbbabbbbbabababbaabababbabaabaaabaababbaabaababababbaabbbabbbbbababababbababaab 
1 length: 150 
2 length: 150 

versione Java: 1.6.0_45 e sto usando la versione di Eclipse: Eclipse IDE Java EE per gli sviluppatori web. Versione: Juno Service Release 2 Corporatura ID: 20130225-0426

UPDATE 1: mi sono imbattuto questa eclissi fuori e ora sembrano essere di corrispondenza, ma io sono sempre ArrayIndexOutOfBoundsException volte:

$ 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 

Anche l'ArrayIndexOutOfBoundsException si sta verificando durante l'esecuzione da eclissi.

UPDATE 2: Ci sono due problemi che si verificano. Il primo problema del contenuto di StringBuilder che non corrisponde alla lunghezza sta accadendo solo in Eclipse e non quando corro in linea di comando (almeno 100 volte in cui l'ho eseguito su riga di comando non è mai successo).

Il secondo problema con ArrayIndexOutOfBoundsException deve riguardare l'implementazione interna della classe StringBuilder, che mantiene un array di caratteri e fa un Arrays.copyOf quando espande la dimensione. Ma mi batte ancora come sta avvenendo una scrittura prima che la dimensione venga espansa, indipendentemente dall'ordine di esecuzione.

BTW, sono propenso a concordare con la risposta di @ GreyBeardedGeek che tutto questo esercizio è un'enorme perdita di tempo :-). A volte arriviamo a vedere solo i sintomi, cioè l'output di qualche codice e ci chiediamo cosa non va.Questa domanda ha dichiarato a priori che due thread stanno modificando un oggetto non sicuro (molto noto) thread.

UPDATE 3: Questa è la risposta ufficiale da Java Concurrency in Practice p. 35:

  • In mancanza di sincronizzazione, il compilatore, processore e runtime può fare alcune cose decisamente strane all'ordine in cui operazioni sembrano eseguire. Tenta di ragionare sull'ordine in quali azioni di memoria "devono" verificarsi in programmi con multithreading non sincronizzati in modo insufficiente sarà quasi certamente errato.

  • Il ragionamento su programmi concorrenti non sincronizzati in modo insufficiente è proibitivo proibitivo .

C'è anche un bell'esempio NoVisibility nel libro a pag. 34.

+0

Questo è strano, sulla mia macchina stampa '199' e' 200' e la dimensione di 'StringBuilder' è uguale al numero dei caratteri. L'ho eseguito più volte ma ottenendo sempre gli stessi risultati –

+0

Anche questo ha qualcosa a che fare con l'IDE. Inizialmente ho usato IntelliJ e dava sempre lunghezze di '199' e' 200'. Successivamente ho eseguito questo in BlueJ e ha dato una lunghezza variabile per ogni corsa. Sembra che IntelliJ sia più bravo a gestire thread multipli rispetto ad altri IDE. –

+0

c'è qualche possibilità di ottenere risultati dall'ID di riga di comando? :) –

risposta

7

Il comportamento di una classe non a prova di codice quando si accede contemporaneamente da più thread è per definizione "non definito".

Qualsiasi tentativo di accertare un comportamento deterministico in tal caso è, IMHO, solo un'enorme perdita di tempo.

+3

Tuttavia penso che questo caso con lunghezza stampata 137 e lunghezza reale 50 sia impossibile anche con comportamento "indefinito". –

0

La discrepanza tra il numero di caratteri stampati e la lunghezza stampata deriva dalla stampa dei valori mentre l'altro thread è ancora in esecuzione. L'errore è correlato alla sincronizzazione ed è causato da entrambi i thread che tentano di modificare lo stesso oggetto nello stesso momento.

Tra il primo e il secondo println l'altro thread ha completato un ciclo aggiuntivo e ha modificato il contenuto del buffer.