2008-11-21 10 views
18

In welchen Fällen muss der Zugriff auf Instanzmitglieder synchronisiert werden? Ich verstehe, dass der Zugriff auf statische Member einer Klasse immer synchronisiert werden muss, da sie für alle Objektinstanzen der Klasse gemeinsam genutzt werden.Welche Fälle erfordern den Zugriff auf synchronisierte Methoden in Java?

Meine Frage ist wann würde ich falsch sein, wenn ich Instanz Mitglieder nicht synchronisieren?

zum Beispiel, wenn meine Klasse

public class MyClass { 
    private int instanceVar = 0; 

    public setInstanceVar() 
    { 
     instanceVar++; 
    } 

    public getInstanceVar() 
    { 
     return instanceVar; 
    } 
} 

in ist, was Fälle (der Nutzung der Klasse MyClass) würde ich müssen Methoden haben: public synchronized setInstanceVar() und public synchronized getInstanceVar()?

Vielen Dank im Voraus für Ihre Antworten.

Antwort

19

Es hängt davon ab, ob Ihre Klasse Thread-sicher sein soll. Die meisten Klassen sollten (der Einfachheit halber) nicht threadsicher sein. In diesem Fall benötigen Sie keine Synchronisation. Wenn Sie es threadsicher benötigen, sollten Sie den Zugriff auf die Variablen oder synchronisieren. (Es vermeidet, dass andere Threads "veraltete" Daten bekommen.)

-3

In Java sind Operationen auf Ints atomar, also nein, in diesem Fall müssen Sie nicht synchronisieren, wenn Sie nur 1 schreiben und 1 lesen eine Zeit.

Wenn diese Longs oder Doubles waren, müssen Sie synchronisieren, weil es möglich ist, einen Teil des Long/Double zu aktualisieren, dann einen anderen Thread lesen, dann endlich den anderen Teil des Long/Double aktualisiert.

+1

Obwohl vorsichtig mit den Operatoren ++ und - sind sie nicht atomar. Um auf der sicheren Seite zu sein, versuchen Sie Klassen von java.util.concurrent.atomic. – InverseFalcon

+0

Operationen für Ganzzahlen sind in Java nicht atomar. –

+0

Sie können ein int aus verschiedenen Threads ändern, lokales Caching in den Threads erhalten, den ++ Vorgang lesen und nicht synchronisieren. Nur weil Longs und Doubles als zwei Op-Operationen gelesen und geschrieben werden (Load Upper und Load Lower, um alle 64 Bits zu erhalten), bedeutet das nicht, dass ganze Zahlen plötzlich atomar werden, weil sie unter 1 weniger Nicht-Thread-Sicherheitsproblemen leiden. – Tatarize

3

Wenn Sie sicher diese Klasse Thread machen mag ich instanceVar als volatile erklären, damit Sie immer den aktuellsten Wert aus dem Speicher erhalten und auch würde ich die setInstanceVar()synchronized machen, weil in der JVM eine Erhöhung nicht eine atomare Operation ist .

private volatile int instanceVar =0; 

public synchronized setInstanceVar() { instanceVar++; 

} 
+3

Das Feld muss nicht flüchtig sein, aber setInstanceVar() muss synchronisiert werden. –

+1

Oder Sie können eine java.util.concurrent.atomic.AtomicInteger verwenden. – InverseFalcon

+1

Daniel: Wenn die getInstanceVar nicht synchronisiert ist, stellt der flüchtige Wert sicher, dass der zurückgegebene Wert neu ist.Aber das wird wahrscheinlich bei den meisten Programmierern verloren gehen. –

1

. Grob gesagt ist die Antwort "es kommt darauf an". Ihre Setter synchronisieren und hier Getter würde nur den beabsichtigten Zweck zu gewährleisten, dass mehrere Threads nicht Variablen zwischen jeweils anderen erhöhen Operationen lesen können:

synchronized increment() 
{ 
     i++ 
} 

synchronized get() 
{ 
    return i; 
    } 

aber das wäre nicht wirklich auch hier arbeiten, denn das ist, um sicherzustellen, Ihr Anrufer Thread wurde der gleiche Wert, den es erhöht, dann würden Sie garantieren müssen, dass Sie atomar sind Erhöhen und dann abrufen, die Sie hier nicht tun - das heißt, Sie würden etwas zu tun haben, wie

synchronized int { 
    increment 
    return get() 
    } 

Grundsätzlich , Synchronisation ist nützlich um zu definieren, welche Operationen garantiert werden müssen, um threadsafe (inotherword) auszuführen s, Sie können keine Situation erzeugen, in der ein separater Thread Ihre Operation unterminiert und Ihre Klasse sich unlogisch verhält oder die Erwartungen an den Zustand der Daten unterminiert. Es ist eigentlich ein größeres Thema als hier angesprochen werden kann.

Dieses Buch Java Concurrency in Practice ist ausgezeichnet und sicherlich viel zuverlässiger als ich.

35

Die synchronized Modifikator ist wirklich eine schlechte Idee und sollte um jeden Preis vermieden werden.Ich denke, es ist lobenswert, dass Sun versucht hat, das Sperren ein wenig einfacher zu machen, aber synchronized verursacht nur mehr Ärger als es wert ist.

Das Problem ist, dass eine synchronized Methode ist eigentlich nur Syntax Zucker für die Sperre auf this und halten es für die Dauer der Methode. Somit würde public synchronized void setInstanceVar() so zu etwas Gleichwertiges sein:

public void setInstanceVar() { 
    synchronized(this) { 
     instanceVar++; 
    } 
} 

Das ist schlecht, aus zwei Gründen:

  • Alle synchronized Methoden innerhalb derselben Klasse genau die gleiche Sperre verwenden, was den Durchsatz
  • reduziert Jeder kann Zugriff auf das Schloss erhalten, einschließlich der Mitglieder anderer Klassen.

Es gibt nichts, mich daran zu hindern, in einer anderen Klasse so etwas wie dies zu tun:

MyClass c = new MyClass(); 
synchronized(c) { 
    ... 
} 

Innerhalb dieses synchronized Blockes, Ich halte die Sperre, die von allen synchronized Methoden innerhalb MyClass erforderlich ist. Dies reduziert den Durchsatz weiter und dramatisch erhöht die Chancen auf einen Deadlock.

Ein besserer Ansatz ist ein spezielles lock Objekt zu haben und den synchronized(...) Block direkt zu verwenden:

public class MyClass { 
    private int instanceVar; 
    private final Object lock = new Object();  // must be final! 

    public void setInstanceVar() { 
     synchronized(lock) { 
      instanceVar++; 
     } 
    } 
} 

Alternativ können Sie die java.util.concurrent.Lock-Schnittstelle und die java.util.concurrent.locks.ReentrantLock Implementierung verwenden können grundsätzlich das gleiche Ergebnis (in der Tat zu erreichen , es ist das gleiche auf Java 6).

+1

@Daniel Ich mochte deine Erklärung sehr, aber sollte es nicht "statisch" erklärt werden, besonders um fadensichere öffentliche Klassen zu haben? Da Sie auch sicherstellen möchten, dass alle Threads zum Erreichen der Sperre dasselbe (gemeinsame) Objekt verwenden, das nicht geändert wird. 'private statische finale Objektsperre = neues Objekt(); // muss statisch sein final' – sactiw

+2

Es ist nicht notwendig, dass 'lock'' statisch' ist, da die Daten, die es synchronisiert ('instanceVar') selbst nicht statisch ist. Static Locks sind fast immer ein Zeichen für sehr, sehr ernsthafte Probleme und Sie sollten sie vermeiden, wann immer es möglich ist. –

+0

@Daniel hmmm ... Ich glaube, ich habe den Punkt übersehen, dass 'MyClass' Thread nicht erweitert, so dass es keine eigenen Threads haben wird. Der Zweck, den Klassen-Thread sicher zu machen, besteht darin, dass einige externe Threads dasselbe Objekt (Instanz) von 'MyClass' teilen, dann sollten sie synchron auf die Instanzvariable zugreifen. Mit anderen Worten, die Sperre hätte als 'statisch' deklariert werden sollen, wenn MyClass als Erweiterung von Thread deklariert wurde. Recht? – sactiw

1

Um es einfach zu sagen, verwenden Sie synchronisiert, wenn mehrere Threads auf die gleiche Methode der gleichen Instanz zugreifen, die den Status des Objekts/der Anwendung ändern wird.

Es ist eine einfache Möglichkeit, Race-Bedingungen zwischen Threads zu verhindern, und eigentlich sollten Sie es nur verwenden, wenn Sie gleichzeitig Threads auf dieselbe Instanz wie ein globales Objekt zugreifen möchten.

Wenn Sie jetzt den Status einer Instanz eines Objekts mit gleichzeitigen Threads lesen, möchten Sie vielleicht in die Datei java.util.concurrent.locks.ReentrantReadWriteLock schauen - die theoretisch viele Threads bei a lesen lässt Zeit, aber nur ein Thread darf schreiben. Im Beispiel der Getter- und Einstellungsmethode, die jeder zu geben scheint, könnten Sie Folgendes tun:

+0

Das Problem mit ReadWriteLock ist, dass es zusätzlichen Aufwand verursacht. Dieser Overhead lohnt sich nur, wenn Sie * lange * Leseoperationen haben, sonst ist es besser, wenn Sie nur eine einzige ReentrantLock verwenden. –