Kann mir jemand anhand eines Beispiels den Vorteil einer synchronisierten Methode gegenüber einem synchronisierten Block erklären?
Kann mir jemand anhand eines Beispiels den Vorteil einer synchronisierten Methode gegenüber einem synchronisierten Block erklären?
Antworten:
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
}
synchronized
Block wird mit zwei Befehlen implementiert, monitorenter
und monitorexit
, sowie ein Ausnahmebehandlungsroutine , die gewährleistet , dass monitorexit
sogar in dem Ausnahmefall genannt. Das ist alles gespeichert, wenn eine synchronized
Methode verwendet wird.
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.
Vorteile:
Nachteile:
Vorteile:
Nachteile:
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.
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.
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.
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
}
}
}
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.
monitorenter
und monitorexit
bevor der Code ausgeführt wird .
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;
}
Mit synchronisierten Blöcken können Sie mehrere Synchronisierer haben, so dass mehrere gleichzeitige, aber nicht widersprüchliche Dinge gleichzeitig ablaufen können.
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);
}
}
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
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 Collection
like Vector
und Hashtable
sie werden synchronisiert, wenn ArrayList
oder HashMap
nicht 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
Der einzige Unterschied: Synchronisierte Blöcke ermöglichen im Gegensatz zur synchronisierten Methode ein granulares Sperren
Grundsätzlich wurden synchronized
Blö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 synchronied
Blö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 Lock
API 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:
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.
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
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
}
}
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 synchronized
Methode 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.
List
kann jedoch zu Leistungsproblemen führen, wenn ein
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.
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
TLDR; Verwenden Sie weder den synchronized
Modifikator noch den synchronized(this){...}
Ausdruck, sondern synchronized(myLock){...}
wo myLock
sich ein Endinstanzfeld befindet, das ein privates Objekt enthält.
Der Unterschied zwischen der Verwendung des synchronized
Modifikators für die Methodendeklaration und dem synchronized(..){ }
Ausdruck im Methodenkörper ist folgender:
synchronized
in der Signatur der Methode angegebene Modifikator
synchronized(this) { .... }
undthis
Objekt 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.synchronized(...){...}
Ausdruck erlaubt es Ihnen
Die Verwendung des synchronized
Modifikators oder synchronized(...) {...}
mit this
als 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 synchronized
empfohlen , weder den Modifikator noch den synchronized(...)
Ausdruck in Verbindung mit this
als 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
}
}
}
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:
Eine verzögerte Initialisierung ist möglich.
Es ist threadsicher.
Nachteile:
- 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:
Eine verzögerte Initialisierung ist möglich.
Es ist auch threadsicher.
Durch synchronisiertes Schlüsselwort reduzierte Leistung wird überwunden.
Nachteile:
Zum ersten Mal kann dies die Leistung beeinträchtigen.
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/
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