Gibt es einen Vorteil, eine synchronisierte Methode anstelle eines synchronisierten Blocks zu verwenden?

401

Kann mir jemand anhand eines Beispiels den Vorteil einer synchronisierten Methode gegenüber einem synchronisierten Block erklären?

Krieger
quelle

Antworten:

431

Kann mir jemand anhand eines Beispiels den Vorteil der synchronisierten Methode gegenüber dem synchronisierten Block erklären? Vielen Dank.

Es gibt keinen klaren Vorteil der Verwendung einer synchronisierten Methode gegenüber dem Block.

Vielleicht ist der einzige (aber ich würde es nicht als Vorteil bezeichnen), dass Sie die Objektreferenz nicht einschließen müssen this .

Methode:

public synchronized void method() { // blocks "this" from here.... 
    ...
    ...
    ...
} // to here

Block:

public void method() { 
    synchronized( this ) { // blocks "this" from here .... 
        ....
        ....
        ....
    }  // to here...
}

Sehen? Überhaupt kein Vorteil.

Blöcke haben jedoch Vorteile gegenüber Methoden, vor allem in Bezug auf die Flexibilität, da Sie ein anderes Objekt als Sperre verwenden können, während die Synchronisierung der Methode das gesamte Objekt sperren würde.

Vergleichen Sie:

// locks the whole object
... 
private synchronized void someInputRelatedWork() {
    ... 
}
private synchronized void someOutputRelatedWork() {
    ... 
}

vs.

// Using specific locks
Object inputLock = new Object();
Object outputLock = new Object();

private void someInputRelatedWork() {
    synchronized(inputLock) { 
        ... 
    } 
}
private void someOutputRelatedWork() {
    synchronized(outputLock) { 
        ... 
    }
}

Auch wenn die Methode wächst, können Sie den synchronisierten Abschnitt weiterhin getrennt halten:

 private void method() {
     ... code here
     ... code here
     ... code here
    synchronized( lock ) { 
        ... very few lines of code here
    }
     ... code here
     ... code here
     ... code here
     ... code here
}
OscarRyz
quelle
44
Ein Vorteil für den Konsumenten der API besteht darin, dass die Verwendung des synchronisierten Schlüsselworts in der Methodendeklaration auch explizit deklariert, dass die Methode auf der Objektinstanz synchronisiert und (vermutlich) threadsicher ist.
Scrubbie
59
Ich weiß, dass dies eine alte Frage ist, aber das Synchronisieren auf "dies" wird in einigen Kreisen als Anti-Muster angesehen. Die unbeabsichtigte Konsequenz ist, dass außerhalb der Klasse jemand eine Objektreferenz sperren kann, die gleich "this" ist, und verhindern kann, dass andere Threads die Barrieren innerhalb der Klasse passieren, was möglicherweise zu einer Deadlock-Situation führt. Erstellen eines "privaten Endobjekts = neues Objekt ();" Variable nur zu Verriegelungszwecken ist die häufig verwendete Lösung. Hier ist eine weitere Frage, die sich direkt auf dieses Problem bezieht.
justin.hughey
30
"Während das Synchronisieren der Methode die gesamte Klasse sperren würde." Das ist nicht richtig. Es wird nicht die gesamte Klasse gesperrt, sondern die gesamte Instanz. Mehrere Objekte derselben Klasse haben alle ihre eigene Sperre. :)
Grüße
4
Interessant daran ist, dass die Verwendung einer synchronisierten Methode dazu führt, dass der generierte Bytecode 1 Anweisung weniger enthält, da Methoden ein synchronisiertes Bit in ihre Signatur eingebrannt haben. Da die Länge des Bytecodes ein Faktor dafür ist, ob eine Methode inline geschaltet wird, kann das Verschieben des Blocks zur Methodensignatur den Unterschied in der Entscheidung ausmachen. Theoretisch sowieso. Ich würde eine Entwurfsentscheidung nicht darauf stützen, dass eine einzelne Bytecode-Anweisung gespeichert wird, das scheint eine schreckliche Idee zu sein. Aber dennoch, es ist ein Unterschied. =)
corsiKa
2
@corsiKa: Sie speichern mehr als eine Anweisung. Ein synchronizedBlock wird mit zwei Befehlen implementiert, monitorenterund monitorexit, sowie ein Ausnahmebehandlungsroutine , die gewährleistet , dass monitorexitsogar in dem Ausnahmefall genannt. Das ist alles gespeichert, wenn eine synchronizedMethode verwendet wird.
Holger
139

Der einzige wirkliche Unterschied besteht darin, dass ein synchronisierter Block auswählen kann, auf welchem ​​Objekt er synchronisiert. Eine synchronisierte Methode kann nur 'this'(oder die entsprechende Klasseninstanz für eine synchronisierte Klassenmethode) verwenden. Diese sind beispielsweise semantisch äquivalent:

synchronized void foo() {
  ...
}

void foo() {
    synchronized (this) {
      ...
    }
}

Letzteres ist flexibler, da es um die zugehörige Sperre eines beliebigen Objekts, häufig einer Mitgliedsvariablen, konkurrieren kann . Es ist auch detaillierter, da vor und nach dem Block möglicherweise gleichzeitig Code ausgeführt wird, der sich jedoch noch in der Methode befindet. Natürlich können Sie eine synchronisierte Methode genauso einfach verwenden, indem Sie den gleichzeitigen Code in separate nicht synchronisierte Methoden umgestalten. Verwenden Sie, was den Code verständlicher macht.

jcrossley3
quelle
Letzteres kann auch sinnvoll sein, wenn nicht der gesamte Code in foo () synchronisiert werden muss.
Evan
1
Dies ist wahr, aber nicht das, was "Warrior" fragte: "Der Vorteil der synchronisierten Methode" gibt es keine.
OscarRyz
76

Synchronisierte Methode

Vorteile:

  • Ihre IDE kann die synchronisierten Methoden angeben.
  • Die Syntax ist kompakter.
  • Erzwingt das Teilen der synchronisierten Blöcke in separate Methoden.

Nachteile:

  • Synchronisiert mit diesem und ermöglicht es so Außenstehenden, auch mit ihm zu synchronisieren.
  • Es ist schwieriger, Code außerhalb des synchronisierten Blocks zu verschieben.

Synchronisierter Block

Vorteile:

  • Ermöglicht die Verwendung einer privaten Variablen für die Sperre und zwingt so die Sperre, innerhalb der Klasse zu bleiben.
  • Synchronisierte Blöcke können durch Suchen von Verweisen auf die Variable gefunden werden.

Nachteile:

  • Die Syntax ist komplizierter und erschwert daher das Lesen des Codes.

Persönlich bevorzuge ich synchronisierte Methoden mit Klassen, die sich nur auf das konzentrieren, was synchronisiert werden muss. Eine solche Klasse sollte so klein wie möglich sein und daher sollte es einfach sein, die Synchronisation zu überprüfen. Andere sollten sich nicht um die Synchronisation kümmern müssen.

iny
quelle
Wenn Sie "in der Klasse bleiben" sagen, meinen Sie "im Objekt bleiben ", oder fehlt mir etwas?
OldPeculier
36

Der Hauptunterschied besteht darin, dass Sie bei Verwendung eines synchronisierten Blocks möglicherweise ein anderes Objekt als dieses sperren , wodurch Sie viel flexibler werden können.

Angenommen, Sie haben eine Nachrichtenwarteschlange und mehrere Nachrichtenproduzenten und -konsumenten. Wir möchten nicht, dass sich die Produzenten gegenseitig stören, aber die Verbraucher sollten in der Lage sein, Nachrichten abzurufen, ohne auf die Produzenten warten zu müssen. Also erstellen wir einfach ein Objekt

Object writeLock = new Object();

Und von nun an setzen wir jedes Mal, wenn ein Produzent eine neue Nachricht hinzufügen möchte, einfach darauf:

synchronized(writeLock){
  // do something
}

So können Verbraucher noch lesen, und Produzenten werden gesperrt.

cdecker
quelle
2
Ihr Beispiel ist auf zerstörungsfreie Lesevorgänge beschränkt. Wenn der Lesevorgang die Nachricht aus der Warteschlange entfernt, schlägt dies fehl, wenn dies zu einem bestimmten Zeitpunkt erfolgt, während ein Produzent in die Warteschlange schreibt.
Ceving
30

Synchronisierte Methode

Synchronisierte Methoden haben zwei Auswirkungen.
Wenn ein Thread eine synchronisierte Methode für ein Objekt ausführt, blockieren zunächst alle anderen Threads, die synchronisierte Methoden für denselben Objektblock aufrufen (Ausführung aussetzen), bis der erste Thread mit dem Objekt fertig ist.

Zweitens wird beim Beenden einer synchronisierten Methode automatisch eine Vorher-Beziehung zu einem nachfolgenden Aufruf einer synchronisierten Methode für dasselbe Objekt hergestellt. Dies garantiert, dass Änderungen am Status des Objekts für alle Threads sichtbar sind.

Beachten Sie, dass Konstruktoren nicht synchronisiert werden können. Die Verwendung des synchronisierten Schlüsselworts mit einem Konstruktor ist ein Syntaxfehler. Das Synchronisieren von Konstruktoren ist nicht sinnvoll, da nur der Thread, der ein Objekt erstellt, während der Erstellung Zugriff darauf haben sollte.

Synchronisierte Anweisung

Im Gegensatz zu synchronisierten Methoden müssen synchronisierte Anweisungen das Objekt angeben, das die intrinsische Sperre bereitstellt: Meistens verwende ich dies, um den Zugriff auf eine Liste oder Zuordnung zu synchronisieren, möchte jedoch nicht den Zugriff auf alle Methoden des Objekts blockieren.

F: Eigensperren und Synchronisation Die Synchronisation basiert auf einer internen Entität, die als Eigensperre oder Monitorsperre bezeichnet wird. (In der API-Spezifikation wird diese Entität häufig einfach als "Monitor" bezeichnet.) Intrinsische Sperren spielen bei beiden Aspekten der Synchronisierung eine Rolle: Erzwingen des exklusiven Zugriffs auf den Status eines Objekts und Herstellen von Vor-Vor-Beziehungen, die für die Sichtbarkeit wesentlich sind.

Jedem Objekt ist eine intrinsische Sperre zugeordnet. Gemäß der Konvention muss ein Thread, der exklusiven und konsistenten Zugriff auf die Felder eines Objekts benötigt, die intrinsische Sperre des Objekts abrufen, bevor er auf sie zugreift, und dann die intrinsische Sperre aufheben, wenn sie mit ihnen abgeschlossen ist. Ein Thread soll die intrinsische Sperre zwischen dem Zeitpunkt, zu dem er die Sperre erworben und die Sperre freigegeben hat, besitzen. Solange ein Thread eine intrinsische Sperre besitzt, kann kein anderer Thread dieselbe Sperre erhalten. Der andere Thread wird blockiert, wenn er versucht, die Sperre zu erlangen.

package test;

public class SynchTest implements Runnable {  
    private int c = 0;

    public static void main(String[] args) {
        new SynchTest().test();
    }

    public void test() {
        // Create the object with the run() method
        Runnable runnable = new SynchTest();
        Runnable runnable2 = new SynchTest();
        // Create the thread supplying it with the runnable object
        Thread thread = new Thread(runnable,"thread-1");
        Thread thread2 = new Thread(runnable,"thread-2");
//      Here the key point is passing same object, if you pass runnable2 for thread2,
//      then its not applicable for synchronization test and that wont give expected
//      output Synchronization method means "it is not possible for two invocations
//      of synchronized methods on the same object to interleave"

        // Start the thread
        thread.start();
        thread2.start();
    }

    public synchronized  void increment() {
        System.out.println("Begin thread " + Thread.currentThread().getName());
        System.out.println(this.hashCode() + "Value of C = " + c);
//      If we uncomment this for synchronized block, then the result would be different
//      synchronized(this) {
            for (int i = 0; i < 9999999; i++) {
                c += i;
            }
//      }
        System.out.println("End thread " + Thread.currentThread().getName());
    }

//    public synchronized void decrement() {
//        System.out.println("Decrement " + Thread.currentThread().getName());
//    }

    public int value() {
        return c;
    }

    @Override
    public void run() {
        this.increment();
    }
}

Überprüfen Sie verschiedene Ausgänge mit synchronisierter Methode, Block und ohne Synchronisation.

Sudheer
quelle
10
+1 für die einzige Erwähnung, dass Konstruktoren nicht synchronisiert werden können . Das heißt, in einem Konstruktor haben Sie wirklich nur eine Option: Synchronisierte Blöcke.
ef2011
Ich habe Ihren Code wie angegeben getestet, aber C ist immer 0, dann -2024260031 und das einzige, was ihn ändert, ist der Hash-Code. Welches Verhalten sollte gesehen werden?
Justin Johnson
Sie sollten die folgenden Artikel zitiert haben, aus denen der Inhalt bereitgestellt wurde: docs.oracle.com/javase/tutorial/essential/concurrency/… und docs.oracle.com/javase/tutorial/essential/concurrency/…
Ravindra babu
29

Hinweis: Statisch synchronisierte Methoden und Blöcke funktionieren für das Class-Objekt.

public class MyClass {
   // locks MyClass.class
   public static synchronized void foo() {
// do something
   }

   // similar
   public static void foo() {
      synchronized(MyClass.class) {
// do something
      }
   }
}
Peter Lawrey
quelle
18

Wenn der Java-Compiler Ihren Quellcode in Bytecode konvertiert, werden synchronisierte Methoden und synchronisierte Blöcke sehr unterschiedlich behandelt.

Wenn die JVM eine synchronisierte Methode ausführt, gibt der ausführende Thread an, dass für die Struktur method_info der Methode das Flag ACC_SYNCHRONIZED gesetzt ist. Anschließend wird die Sperre des Objekts automatisch abgerufen, die Methode aufgerufen und die Sperre aufgehoben. Wenn eine Ausnahme auftritt, gibt der Thread die Sperre automatisch frei.

Das Synchronisieren eines Methodenblocks umgeht andererseits die integrierte Unterstützung der JVM für die Erfassung der Sperr- und Ausnahmebehandlung eines Objekts und erfordert, dass die Funktionalität explizit in Bytecode geschrieben wird. Wenn Sie den Bytecode für eine Methode mit einem synchronisierten Block lesen, werden mehr als ein Dutzend zusätzliche Vorgänge zum Verwalten dieser Funktionalität angezeigt.

Dies zeigt Aufrufe zum Generieren einer synchronisierten Methode und eines synchronisierten Blocks:

public class SynchronizationExample {
    private int i;

    public synchronized int synchronizedMethodGet() {
        return i;
    }

    public int synchronizedBlockGet() {
        synchronized( this ) {
            return i;
        }
    }
}

Die synchronizedMethodGet()Methode generiert den folgenden Bytecode:

0:  aload_0
1:  getfield
2:  nop
3:  iconst_m1
4:  ireturn

Und hier ist der Bytecode der synchronizedBlockGet()Methode:

0:  aload_0
1:  dup
2:  astore_1
3:  monitorenter
4:  aload_0
5:  getfield
6:  nop
7:  iconst_m1
8:  aload_1
9:  monitorexit
10: ireturn
11: astore_2
12: aload_1
13: monitorexit
14: aload_2
15: athrow

Ein wesentlicher Unterschied zwischen der synchronisierten Methode und dem Block besteht darin, dass der synchronisierte Block im Allgemeinen den Umfang der Sperre verringert. Da der Umfang der Sperre umgekehrt proportional zur Leistung ist, ist es immer besser, nur kritische Codeabschnitte zu sperren. Eines der besten Beispiele für die Verwendung eines synchronisierten Blocks ist das doppelt überprüfte Sperren im Singleton-Muster, bei dem nicht das Ganze gesperrt wirdgetInstance() Methode nur kritische Codeabschnitte gesperrt werden, die zum Erstellen der Singleton-Instanz verwendet werden. Dies verbessert die Leistung drastisch, da nur ein oder zwei Mal gesperrt werden muss.

Bei der Verwendung synchronisierter Methoden müssen Sie besonders vorsichtig sein, wenn Sie sowohl statisch synchronisierte als auch nicht statisch synchronisierte Methoden mischen.

Mohammad Adil
quelle
1
Wenn wir uns die bytecode-synchronisierte Methode ansehen, ist der Bytecode kompakter und einfacher. Warum ist dieser synchronisierte Block dann nicht schneller?
eatSleepCode
@eatSleepCode Beachten Sie, dass dies ein Bytecode ist, der von der JVM weiter "kompiliert" wird. Die JVM wird in der erforderlichen hinzufügen monitorenterund monitorexitbevor der Code ausgeführt wird .
Philip Couling
12

Meistens verwende ich dies, um den Zugriff auf eine Liste oder Karte zu synchronisieren, aber ich möchte den Zugriff auf alle Methoden des Objekts nicht blockieren.

Im folgenden Code blockiert ein Thread, der die Liste ändert, nicht das Warten auf einen Thread, der die Map ändert. Wenn die Methoden für das Objekt synchronisiert würden, müsste jede Methode warten, obwohl die von ihnen vorgenommenen Änderungen nicht in Konflikt stehen würden.

private List<Foo> myList = new ArrayList<Foo>();
private Map<String,Bar) myMap = new HashMap<String,Bar>();

public void put( String s, Bar b ) {
  synchronized( myMap ) {
    myMap.put( s,b );
    // then some thing that may take a while like a database access or RPC or notifying listeners
  }
}

public void hasKey( String s, ) {
  synchronized( myMap ) {
    myMap.hasKey( s );
  }
}

public void add( Foo f ) {
  synchronized( myList ) {
    myList.add( f );
// then some thing that may take a while like a database access or RPC or notifying listeners
  }
}

public Thing getMedianFoo() {
  Foo med = null;
  synchronized( myList ) {
    Collections.sort(myList);
    med = myList.get(myList.size()/2); 
  }
  return med;
}
Clint
quelle
7

Mit synchronisierten Blöcken können Sie mehrere Synchronisierer haben, so dass mehrere gleichzeitige, aber nicht widersprüchliche Dinge gleichzeitig ablaufen können.

Paul Tomblin
quelle
6

Synchronisierte Methoden können mithilfe der Reflection-API überprüft werden. Dies kann zum Testen einiger Verträge hilfreich sein, z. B. wenn alle Methoden im Modell synchronisiert sind .

Das folgende Snippet druckt alle synchronisierten Methoden von Hashtable:

for (Method m : Hashtable.class.getMethods()) {
        if (Modifier.isSynchronized(m.getModifiers())) {
            System.out.println(m);
        }
}
Kojotak
quelle
5

Wichtiger Hinweis zur Verwendung des synchronisierten Blocks: Achten Sie darauf, was Sie als Sperrobjekt verwenden!

Das Code-Snippet von user2277816 oben veranschaulicht diesen Punkt dahingehend, dass ein Verweis auf ein Zeichenfolgenliteral als Sperrobjekt verwendet wird. Wenn Sie erkennen, dass Zeichenfolgenliterale automatisch in Java interniert werden, sollten Sie das Problem erkennen: Jeder Code, der mit der wörtlichen "Sperre" synchronisiert wird, hat dieselbe Sperre! Dies kann leicht zu Deadlocks mit völlig unabhängigen Codeteilen führen.

Es sind nicht nur String-Objekte, mit denen Sie vorsichtig sein müssen. Boxed Primitive sind ebenfalls eine Gefahr, da Autoboxing und die valueOf-Methoden je nach Wert dieselben Objekte wiederverwenden können.

Weitere Informationen finden Sie unter: https://www.securecoding.cert.org/confluence/display/java/LCK01-J.+Do+not+synchronize+on+objects+that+may+be+reused

Søren Boisen
quelle
5

Oft ist die Verwendung einer Sperre auf Methodenebene zu unhöflich. Warum einen Code sperren, der nicht auf gemeinsam genutzte Ressourcen zugreift, indem eine gesamte Methode gesperrt wird? Da jedes Objekt über eine Sperre verfügt, können Sie Dummy-Objekte erstellen, um die Synchronisierung auf Blockebene zu implementieren. Die Blockebene ist effizienter, da nicht die gesamte Methode gesperrt wird.

Hier ein Beispiel

Methodenebene

class MethodLevel {

  //shared among threads
SharedResource x, y ;

public void synchronized method1() {
   //multiple threads can't access
}
public void synchronized method2() {
  //multiple threads can't access
}

 public void method3() {
  //not synchronized
  //multiple threads can access
 }
}

Block Level

class BlockLevel {
  //shared among threads
  SharedResource x, y ;

  //dummy objects for locking
  Object xLock = new Object();
  Object yLock = new Object();

    public void method1() {
     synchronized(xLock){
    //access x here. thread safe
    }

    //do something here but don't use SharedResource x, y
    // because will not be thread-safe
     synchronized(xLock) {
       synchronized(yLock) {
      //access x,y here. thread safe
      }
     }

     //do something here but don't use SharedResource x, y
     //because will not be thread-safe
    }//end of method1
 }

[Bearbeiten]

Für Collectionlike Vectorund Hashtablesie werden synchronisiert, wenn ArrayListoder HashMapnicht und Sie müssen das Schlüsselwort synchronized setzen oder die synchronisierte Methode Collections aufrufen:

Map myMap = Collections.synchronizedMap (myMap); // single lock for the entire map
List myList = Collections.synchronizedList (myList); // single lock for the entire list
Maxim Shoustin
quelle
5

Der einzige Unterschied: Synchronisierte Blöcke ermöglichen im Gegensatz zur synchronisierten Methode ein granulares Sperren

Grundsätzlich wurden synchronizedBlöcke oder Methoden verwendet, um thread-sicheren Code zu schreiben, indem Speicherinkonsistenzfehler vermieden wurden.

Diese Frage ist sehr alt und viele Dinge haben sich in den letzten 7 Jahren geändert. Für die Thread-Sicherheit wurden neue Programmierkonstrukte eingeführt.

Sie können Thread-Sicherheit erreichen, indem Sie anstelle von synchroniedBlöcken die erweiterte Parallelitäts-API verwenden . Diese Dokumentation Seite bietet eine gute Programmierkonstrukte Thread - Sicherheit zu erreichen.

Sperrobjekte unterstützen Sperrsprachen, die viele gleichzeitige Anwendungen vereinfachen.

Ausführende definieren eine allgemeine API zum Starten und Verwalten von Threads. Von java.util.concurrent bereitgestellte Executor-Implementierungen bieten eine Thread-Pool-Verwaltung, die für umfangreiche Anwendungen geeignet ist.

Gleichzeitige Sammlungen erleichtern die Verwaltung großer Datensammlungen und können den Synchronisierungsbedarf erheblich reduzieren.

Atomic Variables verfügen über Funktionen, die die Synchronisation minimieren und Speicherkonsistenzfehler vermeiden.

ThreadLocalRandom (in JDK 7) bietet eine effiziente Generierung von Pseudozufallszahlen aus mehreren Threads.

Besserer Ersatz für synchronisierte ist ReentrantLock , das LockAPI verwendet

Eine reentrante Sperre für den gegenseitigen Ausschluss mit demselben grundlegenden Verhalten und derselben Semantik wie die implizite Monitorsperre, auf die mit synchronisierten Methoden und Anweisungen zugegriffen wird, jedoch mit erweiterten Funktionen.

Beispiel mit Schlössern:

class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

Weitere Programmierkonstrukte finden Sie auch in den Paketen java.util.concurrent und java.util.concurrent.atomic .

Siehe auch diese verwandte Frage:

Synchronisation gegen Sperre

Ravindra Babu
quelle
4

Die synchronisierte Methode wird zum Sperren aller Objekte verwendet. Der synchronisierte Block wird zum Sperren eines bestimmten Objekts verwendet

Kishore
quelle
3

Im Allgemeinen sind diese meistens dieselben, außer dass sie explizit den verwendeten Objektmonitor gegenüber dem impliziten Objekt beschreiben. Ein Nachteil von synchronisierten Methoden, von denen ich denke, dass sie manchmal übersehen werden, ist, dass Sie bei Verwendung der Referenz "this" zum Synchronisieren die Möglichkeit offen lassen, dass externe Objekte dasselbe Objekt sperren. Das kann ein sehr subtiler Fehler sein, wenn Sie darauf stoßen. Durch die Synchronisierung auf einem internen expliziten Objekt oder einem anderen vorhandenen Feld kann dieses Problem vermieden und die Synchronisierung vollständig gekapselt werden.

Alex Miller
quelle
2

Wie hier bereits gesagt, kann der synchronisierte Block eine benutzerdefinierte Variable als Sperrobjekt verwenden, wenn die synchronisierte Funktion nur "dies" verwendet. Und natürlich können Sie mit Bereichen Ihrer Funktion manipulieren, die synchronisiert werden sollen. Aber jeder sagt, dass kein Unterschied zwischen synchronisierter Funktion und Block, der die gesamte Funktion abdeckt, "dies" als Sperrobjekt verwendet. Das ist nicht wahr, der Unterschied liegt im Bytecode, der in beiden Situationen generiert wird. Im Falle einer synchronisierten Blocknutzung sollte eine lokale Variable zugewiesen werden, die auf "this" verweist. Infolgedessen haben wir eine etwas größere Funktion (nicht relevant, wenn Sie nur wenige Funktionen haben).

Eine ausführlichere Erklärung des Unterschieds finden Sie hier: http://www.artima.com/insidejvm/ed2/threadsynchP.html

römisch
quelle
2

Bei synchronisierten Methoden wird die Sperre für ein Objekt erworben. Wenn Sie sich jedoch für einen synchronisierten Block entscheiden, können Sie ein Objekt angeben, für das die Sperre erworben werden soll.

Beispiel:

    Class Example {
    String test = "abc";
    // lock will be acquired on String  test object.
    synchronized (test) {
        // do something
    }

   lock will be acquired on Example Object
   public synchronized void testMethod() {
     // do some thing
   } 

   }
Srinu Yarru
quelle
2

Ich weiß, dass dies eine alte Frage ist, aber als ich die Antworten hier schnell gelesen habe, habe ich nicht wirklich gesehen, dass jemand erwähnt hat, dass eine synchronizedMethode manchmal das falsche Schloss sein kann.
Aus der Java-Parallelität in der Praxis (S. 72):

public class ListHelper<E> {
  public List<E> list = Collections.syncrhonizedList(new ArrayList<>());
...

public syncrhonized boolean putIfAbsent(E x) {
 boolean absent = !list.contains(x);
if(absent) {
 list.add(x);
}
return absent;
}

Der obige Code hat das Aussehen von threadsicher sein. In Wirklichkeit ist dies jedoch nicht der Fall. In diesem Fall wird die Sperre für die Instanz der Klasse erhalten. Es ist jedoch möglich, dass die Liste von einem anderen Thread geändert wird, der diese Methode nicht verwendet. Der richtige Ansatz wäre zu verwenden

public boolean putIfAbsent(E x) {
 synchronized(list) {
  boolean absent = !list.contains(x);
  if(absent) {
    list.add(x);
  }
  return absent;
}
}

Der obige Code würde blockieren alle Threads versuchen , zu modifizieren Liste aus Modifizieren der Liste , bis der synchronisierten Block abgeschlossen hat.

aarbor
quelle
Ich lese dieses Buch im Moment ... ich frage mich ... ob diese Liste privat statt öffentlich war und nur die putIfAbsent-Methode hatte, synchronisiert (dies) wäre ausreichend, oder? Das Problem ist, dass die Liste auch außerhalb dieses ListHelper geändert werden kann.
dtc
@dtc Ja, wenn die Liste privat wäre und nirgendwo anders in der Klasse durchgesickert wäre, wäre dies ausreichend, solange Sie jede andere Methode in der Klasse markiert haben, die die Liste ebenfalls als synchronisiert ändert. Das Sperren der gesamten Methode anstelle nur der Methode Listkann jedoch zu Leistungsproblemen führen, wenn ein
Codeprotokoll
Das macht Sinn. Vielen Dank für die Antwort! tbh, ich fand das Buch sehr nützlich, um mein Wissen zu erweitern und wie man mit Multithreading
umgeht
2

In der Praxis besteht der Vorteil synchronisierter Methoden gegenüber synchronisierten Blöcken darin, dass sie idiotenresistenter sind. Da Sie kein beliebiges Objekt zum Sperren auswählen können, können Sie die synchronisierte Methodensyntax nicht missbrauchen, um dumme Dinge wie das Sperren eines Zeichenfolgenliteral oder das Sperren des Inhalts eines veränderlichen Felds zu tun, das unter den Threads geändert wird.

Andererseits können Sie mit synchronisierten Methoden die Sperre nicht davor schützen, von einem Thread erfasst zu werden, der einen Verweis auf das Objekt erhalten kann.

Die Verwendung von synchronisiert als Modifikator für Methoden schützt Ihre Cow-Orker besser vor Verletzungen, während die Verwendung synchronisierter Blöcke in Verbindung mit privaten Objekten für die endgültige Sperre Ihren eigenen Code besser vor den Cow-Orkern schützt.

Nathan Hughes
quelle
1

Aus einer Zusammenfassung der Java-Spezifikationen: http://www.cs.cornell.edu/andru/javaspec/17.doc.html

Die synchronisierte Anweisung (§14.17) berechnet einen Verweis auf ein Objekt. Anschließend wird versucht, eine Sperraktion für dieses Objekt auszuführen, und es wird erst fortgefahren, wenn die Sperraktion erfolgreich abgeschlossen wurde. ...

Eine synchronisierte Methode (§8.4.3.5) führt beim Aufrufen automatisch eine Sperraktion aus. Sein Body wird erst ausgeführt, wenn die Sperraktion erfolgreich abgeschlossen wurde. Wenn es sich bei der Methode um eine Instanzmethode handelt , wird die Sperre gesperrt, die der Instanz zugeordnet ist, für die sie aufgerufen wurde (dh das Objekt, das während der Ausführung des Hauptteils der Methode als solches bezeichnet wird). Wenn die Methode statisch ist , wird die Sperre gesperrt, die dem Class-Objekt zugeordnet ist, das die Klasse darstellt, in der die Methode definiert ist. ...

Basierend auf diesen Beschreibungen würde ich sagen, dass die meisten vorherigen Antworten korrekt sind, und eine synchronisierte Methode könnte besonders nützlich sein für statische Methoden, bei denen Sie ansonsten herausfinden müssten, wie Sie das "Klassenobjekt" erhalten, das die Klasse darstellt, in der sich die Methode befand definiert. "

Bearbeiten: Ich dachte ursprünglich, dies wären Zitate der tatsächlichen Java-Spezifikation. Es wurde klargestellt, dass diese Seite nur eine Zusammenfassung / Erklärung der Spezifikation ist

Josiah Yoder
quelle
1

TLDR; Verwenden Sie weder den synchronizedModifikator noch den synchronized(this){...}Ausdruck, sondern synchronized(myLock){...}wo myLocksich ein Endinstanzfeld befindet, das ein privates Objekt enthält.


Der Unterschied zwischen der Verwendung des synchronizedModifikators für die Methodendeklaration und dem synchronized(..){ }Ausdruck im Methodenkörper ist folgender:

  • Der synchronizedin der Signatur der Methode angegebene Modifikator
    1. ist im generierten JavaDoc sichtbar,
    2. programmatisch bestimmbar ist über Reflexion , wenn eine Methode der Modifikator zum Testen Modifier.SYNCHRONIZED ,
    3. erfordert weniger Eingabe und Einrückung im Vergleich zu synchronized(this) { .... }und
    4. (abhängig von Ihrer IDE) ist in der Klassenübersicht und der Code-Vervollständigung sichtbar.
    5. Verwendet das thisObjekt als Sperre, wenn es für eine nicht statische Methode deklariert wird, oder die einschließende Klasse, wenn es für eine statische Methode deklariert wird.
  • Der synchronized(...){...}Ausdruck erlaubt es Ihnen
    1. um nur die Ausführung von Teilen des Körpers einer Methode zu synchronisieren,
    2. innerhalb eines Konstruktors oder eines ( statischen ) Initialisierungsblocks verwendet werden,
    3. um das Sperrobjekt auszuwählen, das den synchronisierten Zugriff steuert.

Die Verwendung des synchronizedModifikators oder synchronized(...) {...}mit thisals Sperrobjekt (wie in synchronized(this) {...}) hat jedoch den gleichen Nachteil. Beide verwenden eine eigene Instanz als Sperrobjekt für die Synchronisierung. Dies ist gefährlich, da nicht nur das Objekt selbst, sondern auch jedes andere externe Objekt / Code, das einen Verweis auf dieses Objekt enthält, es als Synchronisationssperre mit potenziell schwerwiegenden Nebenwirkungen (Leistungseinbußen und Deadlocks ) verwenden kann.

Daher wird synchronizedempfohlen , weder den Modifikator noch den synchronized(...)Ausdruck in Verbindung mit thisals Sperrobjekt zu verwenden, sondern ein für dieses Objekt privates Sperrobjekt. Zum Beispiel:

public class MyService {
    private final lock = new Object();

    public void doThis() {
       synchronized(lock) {
          // do code that requires synchronous execution
        }
    }

    public void doThat() {
       synchronized(lock) {
          // do code that requires synchronous execution
        }
    }
}

Sie können auch mehrere Sperrobjekte verwenden. Es muss jedoch besonders darauf geachtet werden, dass dies bei verschachtelter Verwendung nicht zu Deadlocks führt.

public class MyService {
    private final lock1 = new Object();
    private final lock2 = new Object();

    public void doThis() {
       synchronized(lock1) {
          synchronized(lock2) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThat() and doMore().
          }
    }

    public void doThat() {
       synchronized(lock1) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThis().
              // doMore() may execute concurrently
        }
    }

    public void doMore() {
       synchronized(lock2) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThis().
              // doThat() may execute concurrently
        }
    }
}
Sebastian Thomschke
quelle
1

Ich nehme an, diese Frage betrifft den Unterschied zwischen Thread Safe Singleton und Lazy-Initialisierung mit Double Check Locking . Ich verweise immer auf diesen Artikel, wenn ich einen bestimmten Singleton implementieren muss.

Nun, dies ist ein Thread Safe Singleton :

// Java program to create Thread Safe 
// Singleton class 
public class GFG  
{ 
  // private instance, so that it can be 
  // accessed by only by getInstance() method 
  private static GFG instance; 

  private GFG()  
  { 
    // private constructor 
  } 

 //synchronized method to control simultaneous access 
  synchronized public static GFG getInstance()  
  { 
    if (instance == null)  
    { 
      // if instance is null, initialize 
      instance = new GFG(); 
    } 
    return instance; 
  } 
} 

Vorteile:

  1. Eine verzögerte Initialisierung ist möglich.

  2. Es ist threadsicher.

Nachteile:

  1. Die Methode getInstance () ist synchronisiert, was zu einer langsamen Leistung führt, da mehrere Threads nicht gleichzeitig darauf zugreifen können.

Dies ist eine Lazy-Initialisierung mit Double Check Locking :

// Java code to explain double check locking 
public class GFG  
{ 
  // private instance, so that it can be 
  // accessed by only by getInstance() method 
  private static GFG instance; 

  private GFG()  
  { 
    // private constructor 
  } 

  public static GFG getInstance() 
  { 
    if (instance == null)  
    { 
      //synchronized block to remove overhead 
      synchronized (GFG.class) 
      { 
        if(instance==null) 
        { 
          // if instance is null, initialize 
          instance = new GFG(); 
        } 

      } 
    } 
    return instance; 
  } 
} 

Vorteile:

  1. Eine verzögerte Initialisierung ist möglich.

  2. Es ist auch threadsicher.

  3. Durch synchronisiertes Schlüsselwort reduzierte Leistung wird überwunden.

Nachteile:

  1. Zum ersten Mal kann dies die Leistung beeinträchtigen.

  2. Als Nachteile. Das Double-Check-Locking-Verfahren ist erträglich, sodass es für Hochleistungs-Multithread-Anwendungen verwendet werden kann.

Weitere Informationen finden Sie in diesem Artikel:

https://www.geeksforgeeks.org/java-singleton-design-pattern-practices-examples/

ofundefined
quelle
-3

Synchronisieren mit Threads. 1) Verwenden Sie NIEMALS synchronisiert (dies) in einem Thread, der nicht funktioniert. Bei der Synchronisierung mit (this) wird der aktuelle Thread als Sperr-Thread-Objekt verwendet. Da jeder Thread unabhängig von anderen Threads ist, erfolgt KEINE Koordination der Synchronisation. 2) Code-Tests zeigen, dass in Java 1.6 auf einem Mac die Methodensynchronisation nicht funktioniert. 3) synchronisiert (lockObj), wobei lockObj ein gemeinsames Objekt aller darauf synchronisierenden Threads ist. 4) ReenterantLock.lock () und .unlock () funktionieren. Siehe dazu Java-Tutorials.

Der folgende Code zeigt diese Punkte. Es enthält auch den thread-sicheren Vektor, der die ArrayList ersetzen würde, um zu zeigen, dass viele Threads, die einem Vektor hinzugefügt werden, keine Informationen verlieren, während dieselben mit einer ArrayList Informationen verlieren können. 0) Der aktuelle Code zeigt einen Informationsverlust aufgrund der Rennbedingungen an. A) Kommentieren Sie die aktuell mit A bezeichnete Linie und kommentieren Sie die darüber liegende A-Linie aus. Führen Sie dann die Methode aus. Die Methode verliert Daten, sollte dies jedoch nicht tun. B) Schritt A umkehren, B auskommentieren und // Block beenden}. Führen Sie dann aus, um die Ergebnisse ohne Datenverlust anzuzeigen. C) Kommentieren Sie B aus, kommentieren Sie C aus. Führen Sie aus, siehe Synchronisieren bei (dies) verliert Daten wie erwartet. Sie haben keine Zeit, alle Variationen zu vervollständigen, hoffen, dass dies hilft. Wenn die Synchronisierung auf (dies) erfolgt oder die Methodensynchronisierung funktioniert, geben Sie bitte an, welche Version von Java und Betriebssystem Sie getestet haben. Vielen Dank.

import java.util.*;

/** RaceCondition - Shows that when multiple threads compete for resources 
     thread one may grab the resource expecting to update a particular 
     area but is removed from the CPU before finishing.  Thread one still 
     points to that resource.  Then thread two grabs that resource and 
     completes the update.  Then thread one gets to complete the update, 
     which over writes thread two's work.
     DEMO:  1) Run as is - see missing counts from race condition, Run severa times, values change  
            2) Uncomment "synchronized(countLock){ }" - see counts work
            Synchronized creates a lock on that block of code, no other threads can 
            execute code within a block that another thread has a lock.
        3) Comment ArrayList, unComment Vector - See no loss in collection
            Vectors work like ArrayList, but Vectors are "Thread Safe"
         May use this code as long as attribution to the author remains intact.
     /mf
*/ 

public class RaceCondition {
    private ArrayList<Integer> raceList = new ArrayList<Integer>(); // simple add(#)
//  private Vector<Integer> raceList = new Vector<Integer>(); // simple add(#)

    private String countLock="lock";    // Object use for locking the raceCount
    private int raceCount = 0;        // simple add 1 to this counter
    private int MAX = 10000;        // Do this 10,000 times
    private int NUM_THREADS = 100;    // Create 100 threads

    public static void main(String [] args) {
    new RaceCondition();
    }

    public RaceCondition() {
    ArrayList<Thread> arT = new ArrayList<Thread>();

    // Create thread objects, add them to an array list
    for( int i=0; i<NUM_THREADS; i++){
        Thread rt = new RaceThread( ); // i );
        arT.add( rt );
    }

    // Start all object at once.
    for( Thread rt : arT ){
        rt.start();
    }

    // Wait for all threads to finish before we can print totals created by threads
    for( int i=0; i<NUM_THREADS; i++){
        try { arT.get(i).join(); }
        catch( InterruptedException ie ) { System.out.println("Interrupted thread "+i); }
    }

    // All threads finished, print the summary information.
    // (Try to print this informaiton without the join loop above)
    System.out.printf("\nRace condition, should have %,d. Really have %,d in array, and count of %,d.\n",
                MAX*NUM_THREADS, raceList.size(), raceCount );
    System.out.printf("Array lost %,d. Count lost %,d\n",
             MAX*NUM_THREADS-raceList.size(), MAX*NUM_THREADS-raceCount );
    }   // end RaceCondition constructor



    class RaceThread extends Thread {
    public void run() {
        for ( int i=0; i<MAX; i++){
        try {
            update( i );        
        }    // These  catches show when one thread steps on another's values
        catch( ArrayIndexOutOfBoundsException ai ){ System.out.print("A"); }
        catch( OutOfMemoryError oome ) { System.out.print("O"); }
        }
    }

    // so we don't lose counts, need to synchronize on some object, not primitive
    // Created "countLock" to show how this can work.
    // Comment out the synchronized and ending {, see that we lose counts.

//    public synchronized void update(int i){   // use A
    public void update(int i){                  // remove this when adding A
//      synchronized(countLock){            // or B
//      synchronized(this){             // or C
        raceCount = raceCount + 1;
        raceList.add( i );      // use Vector  
//          }           // end block for B or C
    }   // end update

    }   // end RaceThread inner class


} // end RaceCondition outter class
user2277816
quelle
1
Synchronisieren mit ‚(this)‘ tut Arbeit und ist nicht ‚den aktuellen Thread als Synchronisierungsobjekt verwenden‘, es sei denn , der aktuelle Objekt einer Klasse ist , die Gewinde erstreckt. -1
Marquis von Lorne