2016-07-22 12 views
1

Ich führe den folgenden Code mehrere Male und festgestellt, dass irgendwann bekomme ich "null" von ArrayList. Ich bin nicht in der Lage zu verstehen, warum dies bei mir passiert, während ich Integer-Wert zum Array hinzufüge.Warum bekomme ich Null-Wert von ArrayList nach dem Hinzufügen von Wert mit Thread

package com; 

import java.util.ArrayList; 
import java.util.List; 

public class test implements Runnable { 

    static List<Integer> ls = new ArrayList<Integer>(); 

    public static void main(String[] args) throws InterruptedException { 
     Thread t1 = new Thread(new test()); 
     Thread t2 = new Thread(new test()); 

     t1.start(); 
     t2.start(); 
     t1.join(); 
     t2.join(); 
     System.out.println(ls.size()); 
     for (int i = 0; i < ls.size(); ++i) { 
      System.out.println(i + " " + ls.get(i)); 
     } 
    } 

    @Override 
    public synchronized void run() { 
     try { 
      for (int i = 0; i < 20; ++i) { 
       ls.add(i); 
       Thread.sleep(5); 
      } 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
    } 
} 

Ausgabe:

36 
0 0 
1 0 
2 1 
3 1 
4 2 
5 2 
6 3 
7 3 
8 4 
9 4 
**10 null** 
11 5 
12 6 
13 6 
14 7 
15 7 
16 8 
17 8 
18 9 
19 9 
20 10 
21 10 
22 11 
23 11 
24 12 
25 12 
26 13 
27 14 
28 15 
29 16 
30 16 
31 17 
32 17 
33 18 
34 19 
35 19 

Ich weiß das, warum die Gesamtgröße 36. Aber ich möchte wissen, warum ich an der 10. Position null bekam.

HINWEIS: Dies wird nicht jedes Mal generiert. Sie müssen diesen Code möglicherweise 5 bis 10 Mal ausführen, um dies zu generieren.

+2

Wissen Sie, wie 'ArrayList' die Größe intern ändert? Verwenden Sie keine Thread-unsicheren Objekte über Threads hinweg. – Savior

+0

"Verwenden Sie einfach keine unsicheren Threads für Threads." Es ist nicht relevant. @Pillar –

+0

Bitte schauen Sie in die Semantik von 'synchronized'. Du benutzt es falsch. 'run()' Methode ist fast nie 'synchronisiert'. – Arkadiy

Antwort

2

Was Sie antreffen, ist etwas, das Race Condition genannt wird.

Die "Rasse" ist innerhalb der Java-Laufzeitbibliothek Code passiert, die das Objekt Sammlung implementiert, die Sie verwenden. (Und, so weit, Sie haben extrem glücklich, dass es nicht das gesamte Programm bringen einstürzen. Das nächste Mal könnte es leicht ... Die Situation ist instabil.)

Wenn Sie beabsichtigen, Verwenden Sie Container-Klassen unter Threads, müssen sicherstellen, dass die Klassen "thread-safe", sind, was bedeutet, dass sie die erforderliche Logik enthalten, damit sie von mehreren Threads gleichzeitig korrekt verwendet werden können. Oder: Sie müssen die entsprechende Logik für den gegenseitigen Ausschluss manuell in Ihrer gesamten Anwendung implementieren. (Der Haupt-Thread muss mit dem Kind Gewinde ineinander greifen, so dass es nicht ls.get() nicht versuchen, während der Faden gleichzeitig ls.add() tun wird.)

Wenn die Klasse Sie verwenden ist nicht Thread-sicher, manchmal werden Sie sehen, eine Anwendung, die eine eigene "Wrapper-Klasse" definiert, die Aufrufe an die nicht-thread-sichere Klasse in einer Logik zum gegenseitigen Ausschließen ihrer eigenen Logik "umschließt", so dass der Wrapper "threadsicher" ist, zumindest für die Zwecke der Anwendung.

(P. S .: Dieses Prinzip gilt für jede Programmiersprache.)

+0

Danke Mike. Ich weiß, wo ich falsch lag –

1

Die entsprechende Quote im Javadoc ist:

Beachten Sie, dass diese Implementierung nicht synchronisiert ist. Wenn mehrere Threads gleichzeitig auf eine ArrayList-Instanz zugreifen und mindestens einer der Threads die Liste strukturell ändert [Elemente hinzufügen oder entfernen], muss sie extern synchronisiert werden.

Aber Sie sind-Synchronisation verwenden, indem die run() Methode synchronized. Also stimmt etwas nicht.

Das Erstellen einer Instanzmethode synchronized entspricht dem Einbetten des Methodenhauptteils in synchronized (this) { ... }.Das Problem ist, dass this in jedem der Fäden verschieden ist, so dass sie nicht ausführen den Block gegenseitig ausschließend: das effektive Verhalten ist nicht anders als mit oder ohne synchronized

Stattdessen fügen einen expliziten synchronized Block, um die Liste mit als Monitor:

public void run() { 
    synchronized (ls) { 
    // Your method body. 
    } 
} 

Alternativ können Sie Ihre aktuellen Code einfach wieder verwenden die test Instanz in beiden Themen:

test t = new test(); 
Thread t1 = new Thread(t); 
Thread t2 = new Thread(t); 

Diese ca. oach bedeutet, dass this in beiden Threads identisch ist, daher wird der synchronisierte Block gegenseitig exklusiv ausgeführt. Es würde auch hier funktionieren, ist aber im Allgemeinen eine spröde Strategie, da Sie einen neuen Thread mit einer neuen Testinstanz erstellen könnten, was bedeutet, dass das gleiche Problem erneut auftreten würde.

Beachten Sie, dass diese beiden Lösungen bedeuten würden, dass die gesamte Schleife für einen Thread vor der Schleife für den anderen Thread ausgeführt würde. Effektiv verhält es sich einfach so, als würde man alles in einem Thread ausführen. Dies liegt daran, dass der Monitor vor Beginn der Schleife erfasst und nach Abschluss der Schleife freigegeben wird: Die Operationen können nicht zwischen den Threads verschachtelt werden.

Eine weitere Alternative ist es, die Liste zu machen synchronisiert:

List<Integer> ls = Collections.synchronizedList(new ArrayList<Integer>()); 

Diese umhüllt die unsynchronisierten Liste, so dass jede einzelne Operation auf der Liste in einem synchronized Block ausgeführt wird. Diese würde ermöglichen die Operationen zu verschachteln, da der einzige sich gegenseitig ausschließende Abschnitt ist der Aufruf an ls.add, nicht die gesamte Schleife.