it-swarm.com.de

Worauf muss ich in kritischen Java-Abschnitten synchronisieren?

In Java ist der idiomatische Weg, kritische Abschnitte im Code zu deklarieren, der folgende:

private void doSomething() {
  // thread-safe code
  synchronized(this) {
    // thread-unsafe code
  }
  // thread-safe code
}

Fast alle Blöcke synchronisieren sich auf this, aber gibt es dafür einen bestimmten Grund? Gibt es noch andere Möglichkeiten? Gibt es Best Practices für das zu synchronisierende Objekt? (wie private Instanzen von Object?)

67
lindelof

Beachten Sie zunächst, dass die folgenden Codeausschnitte identisch sind.

public void foo() {
    synchronized (this) {
        // do something thread-safe
    }
}

und:

public synchronized void foo() {
    // do something thread-safe
}

mach genau dasselbe . Keine Präferenz für eine von ihnen, außer für die Lesbarkeit und den Stil des Codes.

Wenn Sie Methoden oder Codeblöcke synchronisieren, ist es wichtig zu wissen , warum Sie so etwas tun, und Welches Objekt genau sperren Sie und zu welchem ​​Zweck ?.

Beachten Sie auch, dass es Situationen gibt, in denen Sie clientseitige Synchronisierung Codeblöcke verwenden möchten, in denen der von Ihnen angeforderte Monitor (dh das synchronisierte Objekt) nicht unbedingt this ist In diesem Beispiel:

Vector v = getSomeGlobalVector();
synchronized (v) {
    // some thread-safe operation on the vector
}

Ich schlage vor, dass Sie mehr über die parallele Programmierung erfahren. Sie wird Ihnen sehr nützlich sein, wenn Sie genau wissen, was hinter den Kulissen passiert. Sie sollten sich Concurrent Programming in Java ansehen, ein großartiges Buch zu diesem Thema. Wenn Sie sich schnell mit dem Thema befassen möchten, lesen Sie Java Concurrency @ Sun

46
Yuval Adam

Wie frühere Antwortende bereits festgestellt haben, empfiehlt es sich, die Synchronisierung für ein Objekt mit begrenztem Umfang durchzuführen (mit anderen Worten, wählen Sie den restriktivsten Bereich aus, den Sie verwenden können, und verwenden Sie ihn.) Insbesondere ist das Synchronisieren mit this eine schlechte Idee. Es sei denn, Sie möchten den Benutzern Ihrer Klasse die Sperre gewähren.

Ein besonders hässlicher Fall tritt jedoch auf, wenn Sie die Synchronisierung mit einem Java.lang.String wählen. Strings können (und werden in der Praxis fast immer) interniert. Das bedeutet, dass jede Zeichenfolge mit gleichem Inhalt - in der ENTIRE JVM - hinter den Kulissen dieselbe Zeichenfolge ist. Das heißt, wenn Sie einen beliebigen String synchronisieren, sperrt ein anderer (völlig unterschiedlicher) Codeabschnitt, der auch einen String mit demselben Inhalt sperrt, Ihren Code.

Ich habe einmal einen Deadlock in einem Produktionssystem zur Fehlerbehebung untersucht und (sehr schmerzhaft) den Deadlock zu zwei völlig unterschiedlichen Open-Source-Paketen verfolgt, die jeweils in einer Instanz von String synchronisiert wurden, deren Inhalt beide "LOCK" war.

60
Jared

Ich versuche, die Synchronisierung auf this zu vermeiden, da dadurch jeder von außen, der einen Verweis auf dieses Objekt hatte, meine Synchronisation blockieren kann. Stattdessen erstelle ich ein lokales Synchronisationsobjekt:

public class Foo {
    private final Object syncObject = new Object();
    …
}

Jetzt kann ich dieses Objekt für die Synchronisation verwenden, ohne befürchten zu müssen, dass jemand das Schloss "stiehlt".

37
Bombe

Nur um hervorzuheben, dass es auch ReadWriteLocks in Java gibt, die als Java.util.concurrent.locks.ReadWriteLock zu finden sind.

In den meisten Fällen verwende ich mein Sperren als 'zum Lesen' und 'zum Aktualisieren'. Wenn Sie einfach ein synchronisiertes Schlüsselwort verwenden, werden alle Lesevorgänge in derselben Methode/in demselben Codeblock in die Warteschlange gestellt. Nur ein Thread kann gleichzeitig auf den Block zugreifen. 

In den meisten Fällen müssen Sie sich keine Gedanken über Parallelitätsprobleme machen, wenn Sie einfach nur lesen. Wenn Sie gerade schreiben, sorgen Sie sich um gleichzeitige Aktualisierungen (die zum Datenverlust führen) oder das Lesen während eines Schreibvorgangs (Teilaktualisierungen), um die Sie sich sorgen müssen.

Daher ist eine Lese-/Schreibsperre bei der Multithread-Programmierung für mich sinnvoller.

6
Kent Lai

Sie sollten ein Objekt synchronisieren, das als Mutex dienen kann. Wenn die aktuelle Instanz (die this - Referenz) geeignet ist (z. B. kein Singleton), können Sie sie verwenden, da in Java jedes Objekt als Mutex dienen kann.

In anderen Fällen möchten Sie möglicherweise einen Mutex für mehrere Klassen freigeben, wenn alle Instanzen dieser Klassen Zugriff auf dieselben Ressourcen benötigen.

Dies hängt stark von der Umgebung ab, in der Sie arbeiten, und von dem Systemtyp, den Sie erstellen. In den meisten Java EE-Anwendungen, die ich gesehen habe, ist eigentlich keine Synchronisierung erforderlich ...

4
Electric Monk

Persönlich denke ich, dass die Antworten, die darauf bestehen, dass die Synchronisierung mit this niemals oder nur selten korrekt ist, falsch sind. Ich denke, es hängt von Ihrer API ab. Wenn Ihre Klasse eine threadsichere Implementierung ist und Sie dies dokumentieren, sollten Sie this verwenden. Wenn bei der Synchronisierung nicht jede Instanz der Klasse als Ganzes threadsicher beim Aufrufen ihrer öffentlichen Methoden gemacht werden soll, sollten Sie ein privates internes Objekt verwenden. Wiederverwendbare Bibliothekskomponenten fallen häufig in die erstere Kategorie. Sie müssen sorgfältig überlegen, bevor Sie dem Benutzer verbieten, Ihre API in die externe Synchronisation einzuschließen.

Im ersten Fall können mit this mehrere Methoden auf atomare Weise aufgerufen werden. Ein Beispiel ist PrintWriter, bei dem Sie möglicherweise mehrere Zeilen ausgeben möchten (z. B. einen Stack-Trace für die Konsole/den Logger) und sicherstellen möchten, dass sie zusammen angezeigt werden. In diesem Fall ist die Tatsache, dass das Synchronisierungsobjekt intern ausgeblendet wird, ein echtes Problem. Ein weiteres Beispiel sind die synchronisierten Auflistungswrapper. Dort müssen Sie das Auflistungsobjekt selbst synchronisieren, um iterieren zu können. Da die Iteration aus mehreren Methodenaufrufen besteht, können Sie sie nicht vollständig intern schützen .

Im letzteren Fall verwende ich ein einfaches Objekt:

private Object mutex=new Object();

Nachdem ich jedoch viele JVM-Dumps und Stack-Traces gesehen habe, die besagen, dass eine Sperre "eine Instanz von Java.lang.Object ()" ist, muss ich sagen, dass die Verwendung einer inneren Klasse oft hilfreicher ist, als andere vorgeschlagen haben.

Wie auch immer, das sind meine zwei Bits wert.

Bearbeiten: Eine andere Sache, beim Synchronisieren auf this Ich bevorzuge es, die Methoden zu synchronisieren, und halten Sie die Methoden sehr granular. Ich denke, es ist klarer und prägnanter.

4
Lawrence Dol

Bei der Synchronisierung in Java werden häufig Vorgänge in derselben Instanz synchronisiert. Das Synchronisieren von this ist dann sehr idiomatisch, da this eine gemeinsam genutzte Referenz ist, die automatisch zwischen verschiedenen Instanzmethoden (oder Abschnitten davon) in einer Klasse verfügbar ist.

Einen anderen Verweis speziell für das Sperren zu verwenden, beispielsweise durch das Deklarieren und Initialisieren eines privaten Feldes Object lock = new Object(), habe ich nie gebraucht oder gebraucht. Ich denke, es ist nur nützlich, wenn Sie eine externe Synchronisierung von zwei oder mehr nicht synchronisierten Ressourcen in einem Objekt benötigen, obwohl ich immer versuchen würde, eine solche Situation in eine einfachere Form umzuwandeln.

Implizite (synchronisierte Methode) oder explizite synchronized(this) werden auch in den Java-Bibliotheken verwendet. Es ist eine gute Redewendung und sollte, falls zutreffend, immer Ihre erste Wahl sein.

2
eljenso

Was Sie synchronisieren, hängt davon ab, welche anderen Threads, die möglicherweise in Konflikt mit diesem Methodenaufruf geraten, synchronisiert werden können. 

Wenn this ein Objekt ist, das nur von einem Thread verwendet wird, und wir auf ein veränderbares Objekt zugreifen, das von Threads gemeinsam genutzt wird, ist es ein guter Kandidat, dieses Objekt zu synchronisieren. Das Synchronisieren von this hat keinen Sinn, da ein anderer Thread dieses gemeinsam genutzte Objekt möglicherweise ändert weiß nicht einmal this, kennt aber dieses Objekt.

Andererseits ist das Synchronisieren über this sinnvoll, wenn viele Threads gleichzeitig Methoden dieses Objekts aufrufen, z. B. wenn wir uns in einem Singleton befinden.

Beachten Sie, dass eine syncronized-Methode oft nicht die beste Option ist, da wir die gesamte Laufzeit der Methode sperren. Wenn es zeitraubende, aber Thread-sichere Teile und einen nicht so zeitaufwendigen Thread-unsicheren Teil enthält, ist das Synchronisieren über die Methode sehr falsch.

1

die Best Practices bestehen darin, ein Objekt nur zur Bereitstellung der Sperre zu erstellen:

private final Object lock = new Object();

private void doSomething() {
  // thread-safe code
  synchronized(lock) {
    // thread-unsafe code
  }
  // thread-safe code
}

Auf diese Weise sind Sie sicher, dass kein aufrufender Code Ihre Methode durch eine unbeabsichtigte synchronized(yourObject) -Zeile blockieren kann.

( Dank an @jared und @ yuval-adam, die dies oben ausführlicher erklärt haben. )

Ich vermute, dass die Popularität der Verwendung von this in Tutorials von Sun stammt: https://docs.Oracle.com/javase/tutorial/essential/concurrency/locksync.html

0
epox

Fast alle Blöcke synchronisieren sich darauf, aber gibt es dafür einen bestimmten Grund? Gibt es noch andere Möglichkeiten?

Diese Deklaration synchronisiert die gesamte Methode.

private synchronized void doSomething() {

Diese Deklaration synchronisierte einen Teil des Codeblocks anstelle der gesamten Methode.

private void doSomething() {
  // thread-safe code
  synchronized(this) {
    // thread-unsafe code
  }
  // thread-safe code
}

Aus der Oracle-Dokumentation Seite

das Synchronisieren dieser Methoden hat zwei Auswirkungen:

Erstens ist es nicht möglich, dass zwei Aufrufe synchronisierter Methoden für dasselbe Objekt verschachtelt werden. Wenn ein Thread eine synchronisierte Methode für ein Objekt ausführt, werden alle anderen Threads, die synchronisierte Methoden für denselben Objektblock aufrufen (Suspend-Ausführung), bis der erste Thread mit dem Objekt fertig ist.

Gibt es noch andere Möglichkeiten? Gibt es Best Practices für das zu synchronisierende Objekt? (wie private Instanzen von Object?)

Es gibt viele Möglichkeiten und Alternativen zur Synchronisation. Sie können Ihren Codethread durch Verwendung von Parallelität APIs (verfügbar seit Version JDK 1.5) sicherer machen.

Lock objects
Executors
Concurrent collections
Atomic variables
ThreadLocalRandom

Weitere Einzelheiten finden Sie unter den SE-Fragen:

Synchronisation vs. Sperre

Vermeiden Sie (dies) in Java synchronisiert?

0
Ravindra babu