2015-10-29 9 views
9

Hier habe ich eine Klasse, die zwei Threads hat, die Zugriff auf eine Liste haben. Ein Thread ersetzt die Liste regelmäßig durch eine aktualisierte Kopie und der andere Thread zeichnet den Inhalt der Liste auf dem Bildschirm.Muss ich den Zugriff auf eine Liste synchronisieren, die nur von einem Thread geändert wird?

public class ThreadSafePainter { 
    private List<String> dataList = new ArrayList<>(); 

    /* 
    * starts a thread to periodically update the dataList 
    */ 
    public ThreadSafePainter() { 
     Thread thread = new Thread(() -> { 
      while (true) { 
       // replace out-dated list with the updated data 
       this.dataList = getUpdatedData(); 
       // wait a few seconds before updating again 
       Thread.sleep(5000); 
      } 
     }); 
     thread.start(); 
    } 

    /* 
    * called 10 times/second from a separate paint thread 
    * Q: Does access to dataList need to be synchronized? 
    */ 
    public void onPaint(Graphics2D g) { 
     Point p = new Point(20, 20); 

     // iterate through the data and display it on-screen 
     for (String data : dataList) { 
      g.drawString(data, p.x, p.y); 
      p.translate(0, 20); 
     } 
    } 

    /* 
    * time consuming data retrieval 
    */ 
    private List<String> getUpdatedData() { 
     List<String> data = new ArrayList<>(); 
     // retrieve external data and populate list 
     return data; 
    } 
} 

Meine Frage ist, muss ich den Zugriff auf die DataList synchronisieren? Wie soll ich das machen? Funktioniert das:

public ThreadSafePainter() { 
    ... 
      synchronized (this) { 
       this.dataList = getUpdatedData(); 
      } 
    ... 
} 

public void onPaint(Graphics2D g) { 
    ... 
    synchronized (this) { 
     for (String data : dataList) 
      ... 
    } 
} 
+0

Malen Sie den gesamten Bildschirm bei jeder Iteration neu? – StackFlowed

+5

Da 'getUpdatedData()' jedes Mal eine neue Liste erstellt, benötigen Sie nur eine sichere Publikation. In diesem Fall wäre es ausreichend, das Feld "dataList" als "volatile" zu deklarieren. Es ist wichtig, dass dies funktioniert, wenn der Listenverweis gespeichert wird, nachdem er ausgefüllt wurde und nie wieder geändert wird (da das nächste Update eine neue Liste erstellt) und der Leser die Referenz einmal pro Verarbeitung liest (wie 'for (...: dataList) 'tut). Wenn es während einer 'Farbe' mehrmals auf die Liste zugreifen muss, muss es diese in einer lokalen Variablen speichern. – Holger

+4

Immer wenn zwei oder mehr Threads einen ** mutable ** -Zustand teilen, muss ** eine Art Mechanismus zur Handhabung der Parallelität sein. Ob es sich um Low-Level-Synchronisation, höhere Parallelitätsklassen, "Atomic" -Klassen oder "flüchtige" Felder handelt, hängt von der tatsächlichen Situation ab, aber etwas muss immer an Ort und Stelle sein. – biziclop

Antwort

-1

Die offizielle Java-Dokumentation weist darauf hin, dass eine ArrayList nicht synchronisiert ist. Sie müssen es also synchronisieren.

Die Dokumentation besagt jedoch auch, dass dies nur gilt, wenn mehrere Threads auf die gleiche Liste zugreifen. Also sollte es in Ihrem Fall nicht notwendig sein, es zu synchronisieren. Aber wenn Sie möchten, zu 100% sicher, dass Sie Ihre Liste mit diesem einfachen Aufruf synchronisieren können:

List<data_type> list = Collections.synchronizedList(new ArrayList<data_type>()); 

... wo „data_type“ ist der Typ Werte gespeichert werden sollen.

+5

Diese Dokumentation ist irreführend. Selbst wenn die Liste nicht synchronisiert werden muss, müssen Sie den Verweis auf die Liste auf sichere Weise veröffentlichen. – biziclop

0

Wenn Sie die Liste nicht synchronisieren, sollten Sie die Liste volatil machen. Auf diese Weise erhält der Lese-Thread die letzte Kopie des Werts der List-Variablen.

Here ist eine gute Erklärung.

1

Jedes Mal, wenn Sie mehr als einen Thread auf den gleichen veränderlichen Zustand zugreifen (gut, fast immer, gibt es einige Ausnahmen, wie wenn Sie wissen, dass der Zustand nicht innerhalb der anderen Thread-Lebensdauer mutieren), müssen Sie nehmen einige Art der Aktion. In diesem Fall mutieren Sie das Feld dataList und Sie erwarten, dass ein anderer Thread darauf reagiert. Also musst du etwas machen. Die allgemeinste Lösung ist, synchronized zu verwenden, und Ihre Umrisse, wie man das tut, ist gut.

Wenn Sie Squeeze maximale Leistung aus etwas verwenden möchten (was für ein GUI-Problem irgendwie lächerlich ist), oder Sie Ihr großes Verständnis von Nebenläufigkeit zeigen möchten, können Sie leichtere Alternativen in Erwägung ziehen begrenztere Umstände. In diesem Fall haben Sie nur einen Schreiber und der Schreiber schreibt nur eine einzige Referenz. Für solche Fälle ist volatile ausreichend. In dieser Art von Code würde ich persönlich einfach bei synchronized bleiben, weil es weniger wahrscheinlich ist, zu brechen, wenn Sie den Code ändern, wie vielleicht fügen Sie einen anderen Verfassergewinde oder etwas hinzu.