Frage

Immer wenn auf SO eine Frage zur Java-Synchronisierung auftaucht, möchten einige Leute unbedingt darauf hinweisen synchronized(this) sollte vermieden werden.Stattdessen sei eine Sperrung einer privaten Referenz vorzuziehen.

Einige der genannten Gründe sind:

Andere Leute, mich eingeschlossen, argumentieren so synchronized(this) ist eine Redewendung, die häufig verwendet wird (auch in Java-Bibliotheken), sicher und gut verständlich ist.Dies sollte nicht vermieden werden, da Sie einen Fehler haben und keine Ahnung haben, was in Ihrem Multithread-Programm vor sich geht.Mit anderen Worten:Wenn es anwendbar ist, dann verwenden Sie es.

Ich bin daran interessiert, einige Beispiele aus der Praxis (kein Foobar-Zeug) zu sehen, bei denen eine Sperre vermieden werden kann this ist vorzuziehen, wenn synchronized(this) würde auch den Job machen.

Daher: sollten Sie immer vermeiden synchronized(this) und es durch eine Sperre für eine private Referenz ersetzen?


Einige weitere Informationen (aktualisiert, sobald Antworten vorliegen):

  • Wir sprechen von Instanzsynchronisierung
  • beides implizit (synchronized Methoden) und explizite Form von synchronized(this) gelten als
  • Wenn Sie Bloch oder andere Experten zu diesem Thema zitieren, lassen Sie nicht die Teile weg, die Ihnen nicht gefallen (z. B.Effektives Java, Artikel zur Thread-Sicherheit: Normalerweise ist es die Sperre für die Instanz selbst, es gibt jedoch Ausnahmen.)
  • wenn Sie eine andere Granularität Ihrer Sperre benötigen als synchronized(this) bietet also synchronized(this) ist nicht anwendbar, also ist das nicht das Problem
War es hilfreich?

Lösung

Ich werde jeden Punkt einzeln abdecken.

  1.   

    Einige böse Code kann Ihr Schloss stehlen (sehr beliebt diese, hat auch eine   "Versehentlich" Variante)

    Ich bin mehr Sorgen um versehentlich . Was es hinausläuft, ist, dass diese Verwendung von this Teil Ihrer Klasse ausgesetzt Schnittstelle ist, und soll dokumentiert werden. Manchmal ist die Fähigkeit anderen Code Ihr Schloss verwenden gewünscht wird. Dies gilt für Dinge wie Collections.synchronizedMap (siehe javadoc).

  2.   

    Alle synchronisierten Methoden innerhalb der gleichen Klasse verwenden, die genau die gleichen   Sperre, die den Durchsatz

    reduziert

    Das ist zu einfach Denken; gerade erst von synchronized(this) befreien wird das Problem nicht lösen. Die richtige Synchronisation für Durchsatz wird mehr Gedanken nehmen.

  3.   

    Sie sind (unnötig) zu viel Information aussetzt

    Dies ist eine Variante von # 1. Die Verwendung von synchronized(this) ist Teil Ihrer Schnittstelle. Wenn Sie nicht wollen / müssen diese ausgesetzt sind, tun es nicht.

Andere Tipps

Nun, erstens sollte darauf hingewiesen werden, dass:

public void blah() {
  synchronized (this) {
    // do stuff
  }
}

ist semantisch äquivalent zu:

public synchronized void blah() {
  // do stuff
}

das ist ein Grund, nicht synchronized(this) zu verwenden. Man könnte argumentieren, dass man Sachen rund um den synchronized(this) Block tun können. Der üblicher Grund ist, die versuchen zu vermeiden und die synchronisierte Kontrolle überhaupt zu tun, die für alle Arten von Concurrency Problemen führt, insbesondere die doppelt geprüft sichernde Problem , die zeigen, geht nur, wie schwierig es eine relativ einfache Prüfung THREAD zu machen sein.

Ein privater Sperre ist ein defensiver Mechanismus, der nie eine schlechte Idee ist.

Auch, wie Sie angedeutet, können private Schlösser Granularität steuern. Eine Reihe von Operationen an einem Objekt könnte völlig unabhängig von anderen sein, aber synchronized(this) wird für beide Seiten ausschließen Zugriff auf alle von ihnen.

synchronized(this) gerade wirklich nicht geben Ihnen nichts.

Während Sie synchronisiert verwenden (diese) können Sie die Klasseninstanz als Sperre selbst verwenden. Das bedeutet, dass während Sperre wird übernommen von Thema 1 , die Faden 2 sollte warten.

Nehmen wir den folgenden Code:

public void method1() {
    // do something ...
    synchronized(this) {
        a ++;      
    }
    // ................
}


public void method2() {
    // do something ...
    synchronized(this) {
        b ++;      
    }
    // ................
}

Methode 1 die Variable Modifizieren a und Verfahren 2 die Variable Modifizieren b , um die gleichzeitige Änderung der gleichen Variablen von zwei Fäden vermieden werden sollte und es ist. Aber während thread1 Modifizieren a und thread2 Modifizieren b es kann ohne Race-Bedingung durchgeführt werden.

Leider ist der obige Code wird nicht zulassen, da wir den gleichen Bezug für ein Schloss verwenden; Dies bedeutet, dass Fäden, auch wenn sie nicht in einem Rennen Zustand sind, sollten Sie den Code Opfer Gleichzeitigkeit des Programms warten und offensichtlich.

Die Lösung zu verwenden ist 2 verschiedene Schlösser für zwei verschiedene Variablen:

public class Test {

    private Object lockA = new Object();
    private Object lockB = new Object();

    public void method1() {
        // do something ...
        synchronized(lockA) {
            a ++;      
        }
        // ................
    }


    public void method2() {
        // do something ...
        synchronized(lockB) {
            b ++;      
        }
        // ................
    }

}

Das obige Beispiel verwendet mehr feinkörnige Schlösser (2 Schlösser anstelle einer ( locka und lockB für Variablen a und b respectively) und als Ergebnis komplexer wurde es auf der anderen Seite besser Gleichzeitigkeit ermöglicht als das erste Beispiel ...

Während ich die Einhaltung etwa nicht blind zu dogmatischen Regeln nicht einverstanden ist, funktioniert die „Sperre stehlen“ Szenario scheint so exzentrisch zu Ihnen? Ein Thread in der Tat könnte die Sperre für das Objekt „extern“ (synchronized(theObject) {...}) erwerben, blockiert andere Threads auf synchronisierte Instanzmethoden warten.

Wenn Sie nicht in bösartigem Code glauben, die Meinung, dass dieser Code von Dritten kommen könnte (zum Beispiel, wenn Sie irgendeine Art von Anwendungsserver entwickeln).

Die „zufällige“ Version scheint weniger wahrscheinlich, aber wie sie sagen, „etwas idiotensicher machen und jemand einen besseren Idiot erfinden“.

Also habe ich mit der it-hängt-on-what-the-Klasse-does Denkschule zustimmen.


Bearbeiten folgenden eljenso ersten 3 Kommentare:

Ich habe noch nie die Sperre stiehlt Problem erlebt, aber hier ist ein imaginäres Szenario:

Nehmen wir an, Ihr System ein Servlet-Container ist, und das Objekt, das wir erwägen, ist die ServletContext Umsetzung. Seine getAttribute Methode muss threadsicher sein, als Kontextattribute Daten gemeinsam genutzt werden; so erklären Sie es als synchronized. Lassen Sie sich auch vorstellen, dass Sie einen öffentlichen Hosting-Service auf der Grundlage Ihrer Container-Implementierung.

Ich bin Ihr Kunde und bereitstellen mein „gut“ Servlet auf Ihrer Website. Es kommt vor, dass mein Code einen Aufruf enthält getAttribute.

Ein Hacker, als ein anderer Kunde verkleidet, entfaltet seine bösartige Servlet auf Ihrer Website. Es enthält den folgenden Code in der init Methode:

synchronized (this.getServletConfig().getServletContext()) {
   while (true) {}
}

wir den gleichen Servlet-Kontext teilen Unter der Annahme (durch die Spezifikation erlaubt, solange die beiden Servlets auf dem gleichen virtuellen Host) sind, mein Ruf auf getAttribute ist für immer gesperrt. Der Hacker erreicht einen DoS auf meinem Servlet.

Dieser Angriff ist nicht möglich, wenn getAttribute auf einem privaten Schloss synchronisiert ist, da 3rd-Party-Code diese Sperre nicht erwerben können.

Ich gebe zu, dass das Beispiel gekünstelt ist und eine oversimplistic Ansicht, wie ein Servlet-Container funktionieren, aber IMHO beweist es den Punkt.

So würde ich meine Design-Wahl basiert auf Sicherheit Überlegung: werde ich die vollständige Kontrolle über den Code, der den Zugang zu den Instanzen hat? Was wäre die Folge ein Thread eine Sperre auf eine Instanz halten auf unbestimmte Zeit?

Es scheint einen anderen Konsens in dem C # und Java-Camps auf diesem Die Mehrheit des Java-Code Ich habe Anwendungen gesehen.

// apply mutex to this instance
synchronized(this) {
    // do work here
}

, während die Mehrheit der C # -Code entscheidet sich für die wohl sicherer:

// instance level lock object
private readonly object _syncObj = new object();

...

// apply mutex to private instance level field (a System.Object usually)
lock(_syncObj)
{
    // do work here
}

Das C # Idiom ist sicherlich sicherer. Wie bereits erwähnt, kann kein bösartiger / versehentliche Zugriff auf die Sperre von außerhalb der Instanz vorgenommen werden. Java-Code hat dieses Risiko auch , aber es scheint, dass die Java-Community im Laufe der Zeit auf die etwas weniger sicher gravitierte hat, aber etwas kurz und bündig Version.

Das ist nicht als dig gegen Java gemeint, nur ein Spiegelbild meiner Erfahrung auf beiden Sprachen zu arbeiten.

Es hängt von der Situation ab.
Wenn es nur eine gemeinsame Nutzung Einheit oder mehr als eine.

Komplettes Arbeitsbeispiel hier

Eine kleine Einführung.

Themen und gemeinsam nutzbare Einheiten
Es ist möglich, mehrere Threads derselben Einheit zuzugreifen, zB für mehrere connectionThreads einen einzelnen Message teilen. Da die Threads gleichzeitig dort laufen eine Chance sein, kann man die Daten durch ein anderes zu überschreiben, die eine nach oben Situation verwirrt werden kann.
Also müssen wir einen Weg, um sicherzustellen, dass gemeinsam nutzbare Einheit zu einem Zeitpunkt nur von einem Thread zugegriffen wird. (CONCURRENCY).

Synchronized Block
(Synchronisiert) Block ist ein Weg, den gleichzeitigen Zugriff von gemeinsam nutzbaren Einheit zu gewährleisten.
Zunächst wird eine kleine Analogie
Angenommen, es gibt Zwei-Personen-P1, P2 (Fäden) ein Waschbecken (gemeinsam nutzbare Einheit) in einem Waschraum und es gibt eine Tür (Schloss).
Jetzt wollen wir eine Person Waschbecken zu einem Zeitpunkt, zu verwenden.
Ein Ansatz ist es, die Tür von P1 zu sperren, wenn die Tür P2 wartet gesperrt ist, bis p1 seine Arbeit
abgeschlossen P1 entriegelt die Tür
p1 kann dann nur Waschbecken verwenden.

Syntax.

synchronized(this)
{
  SHARED_ENTITY.....
}

„this“ vorgesehen, um die intrinsische Sperre mit der Klasse verbunden sind (Java-Entwickler entwickelt Objektklasse in einer solchen Art und Weise, dass jedes Objekt kann als Monitor arbeiten). Above Ansatz funktioniert gut, wenn es nur eine gemeinsame Einheit und mehrere Threads sind (1: N).
   „eingeben N gemeinsam nutzbare Einheiten-M Fäden
Jetzt denken Sie an eine Situation, wenn zwei Waschbecken dort in einem Waschraum ist und nur eine Tür. Wenn wir den bisherigen Ansatz verwenden, kann nur p1 ein Waschbecken zu einer Zeit, während p2 außen warten. Es ist Verschwendung von Ressourcen, da niemand wird mit B2 (Waschbecken).
Ein weiser Ansatz wäre, einen kleineren Raum im Inneren Waschraum zu schaffen und ihnen eine Tür pro Waschbecken zur Verfügung stellen. Auf diese Weise kann B1 P1 und P2 zugreifen kann B2 und umgekehrt gelangen.

washbasin1;  
washbasin2;

Object lock1=new Object();
Object lock2=new Object();

  synchronized(lock1)
  {
    washbasin1;
  }

  synchronized(lock2)
  {
    washbasin2;
  }


eingeben Bild Beschreibung hier

Sehen Sie mehr über Themen ----> hier

Das java.util.concurrent Paket hat in beträchtlichem Ausmaß die Komplexität meines Thread-sicher Code reduziert. Ich habe nur vereinzelte Hinweise darauf zu gehen, aber die meisten Arbeit, die ich mit synchronized(x) gesehen haben scheint zu Neuimplementierung eines Lock, Semaphore oder Riegel, jedoch unter Verwendung der untergeordneten Monitore.

In diesem Sinne, die Synchronisierung mit jeder dieser Mechanismen auf einem internen Objekt zu synchronisieren analog ist, anstatt eine Sperre undicht. Dies ist von Vorteil, dass Sie absolute Gewissheit haben, dass Sie den Eintrag in den Monitor steuern, indem zwei oder mehr Threads.

  1. Machen Sie Ihre Daten unveränderlich, wenn es möglich ist (final Variablen)
  2. Wenn Sie nicht Mutation von gemeinsam genutzten Daten über mehrere Threads vermeiden können, verwenden Sie hohe Programmierkonstrukte [z granularer Lock API]
  

Eine Sperre bietet exklusiven Zugriff auf eine gemeinsam genutzte Ressource. Nur einen Thread zu einer Zeit, kann das Schloss und die gesamten Zugriff auf die gemeinsam genutzte Ressource erwerben erfordert, dass das Schloss zuerst erworben werden

Beispielcode verwenden ReentrantLock die Lock Schnittstelle

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

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

Die Vorteile der Sperre über Synchronized (this)

  1. Die Verwendung von synchronisierten Methoden oder Aussagen Kräften alles Verriegelungs Erfassung und Freigabe in einem block strukturiert auftreten.

  2. Lock-Implementierungen bieten zusätzliche Funktionalität über die Verwendung von synchronisierten Methoden und Aussagen, indem

    1. Ein nicht-blockierende Versuch, eine Sperre (tryLock())
    2. zu erwerben
    3. Ein Versuch, das Schloss zu erwerben, die unterbrochen werden kann (lockInterruptibly())
    4. Ein Versuch, die Sperre zu erwerben, die Timeout kann (tryLock(long, TimeUnit)).
  3. Ein Lock-Klasse kann auch das Verhalten und Semantik liefern, die sie von der impliziten Monitorverriegelung, wie

    ist ganz anders
    1. garantierte Bestellung
    2. Nicht-Wieder Teilnehmer Nutzung
    3. Deadlock Erkennung

Haben Sie einen Blick auf diese SE Frage in Bezug auf verschiedene Arten von Locks:

Synchronisation vs sperren

Sie können Thread-Sicherheit mithilfe von erweiterten Gleichzeitigkeit API statt Synchronied Blöcke erreichen. Diese Dokumentation bietet gute Programmierkonstrukte Thread-Sicherheit zu erreichen.

Sperrobjekte Unterstützung Verriegelungs Idiome, die viele gleichzeitige Anwendungen vereinfachen.

Pfänder definieren ein High-Level-API für die Einleitung und Threads zu verwalten. Executor Implementierungen von java.util.concurrent bereitgestellt bietet Thread-Pool-Management geeignet für großflächige Anwendungen.

Concurrent Collections macht es einfacher, große Sammlungen von Daten zu verwalten, und die Notwendigkeit für die Synchronisation erheblich reduzieren kann.

Atomic Variablen haben Eigenschaften, die Synchronisation minimieren und helfen Speicherkonsistenzfehler zu vermeiden.

ThreadLocalRandom (in JDK 7) bietet eine effiziente Erzeugung von Pseudo-Zufallszahlen von mehreren Threads.

Siehe java.util .concurrent und java.util.concurrent.atomic Pakete auch für andere Programmierkonstrukte.

Wenn Sie schon entschieden, dass:

  • das, was Sie tun müssen, ist sperren auf das aktuelle Objekt; und
  • Sie wollen sperren sie mit Granularität kleiner als eine ganze Methode;

dann sehe ich das ein Tabu über synchronizezd (this) nicht.

Einige Leute absichtlich synchronisiert nachfolgend () (anstelle das Verfahren Synchronized der Markierung) in dem gesamten Inhalt einer Methode, weil sie denken, es ist „klarer an den Leser“, der Gegenstand tatsächlich auf synchronisiert wird. Solange Menschen eine fundierte Wahl treffen (zB verstehen, dass, indem Sie so sind sie tatsächlich zusätzliche Bytecodes in das Verfahren eingesetzt und dies eine Sogwirkung auf mögliche Optimierungen haben könnte), sehe ich nicht besonders ein Problem mit diesem . Sie sollten immer das gleichzeitige Verhalten Ihres Programms dokumentieren, so sehe ich nicht das „‚synchronisiert‘veröffentlicht das Verhalten“ Argument als so überzeugend zu sein.

In Bezug auf die Frage, welche Gegenstand des Schlosses Sie verwenden sollten, denke ich, es ist nichts falsch mit Synchronisation auf das aktuelle Objekt , wenn diese durch die Logik zu erwarten wäre, von dem, was Sie tun und wie Sie Ihre Klasse würde typischerweise verwendet . Zum Beispiel mit einer Sammlung, das Objekt, das Sie logisch sperren würden erwarten, ist in der Regel die Sammlung selbst.

Ich denke, es gibt eine gute Erklärung dafür ist, warum jeder von ihnen sind wichtige Techniken unter dem Gürtel in einem Buch mit dem Titel Java Concurrency in der Praxis von Brian Goetz. Er macht einen sehr klaren Punkt - Sie die gleiche Sperre verwenden müssen „ÜBERALL“, um den Zustand des Objekts zu schützen. Synchronisierte Verfahren und Synchronisieren auf ein Objekt gehen oft Hand in Hand. Z.B. Vector synchronisiert alle ihre Methoden. Wenn Sie ein Handle auf ein Vektorobjekt haben und tun werden „setzen, wenn abwesend“ dann nur Vector seinen eigenen Methoden Synchronisierung wird Ihnen von Korruption des Staates nicht zu schützen. Sie müssen mit synchronisierten (vectorHandle) synchronisieren. Diese werden in der gleichen Sperre führen von jedem Thread erlangt wurde, die einen Griff zum Vektor hat und Gesamtzustand des Vektors schützen. Dies wird Client-Seite Verriegelung bezeichnet. Wir wissen, wie eine Tatsache, Vektor nicht synchronisiert (this) / synchronisiert alle ihre Methoden und damit auf das Objekt zu synchronisieren vectorHandle in die richtige Synchronisation des Vektors führen Objekte Zustand. Seine töricht glauben, dass Sie Thread sind sicher, nur weil Sie einen Thread sichere Sammlung verwenden. Dies ist genau der Grund, ConcurrentHashMap ausdrücklich putIfAbsent Verfahren eingeführt -. Auf solche Operationen atomar machen

Insgesamt

  1. bei Methode Ebene Synchronisieren ermöglicht Client-Seite Verriegelung.
  2. Wenn Sie ein privates Sperrobjekt - es macht Clientseite Verriegelung unmöglich. Das ist in Ordnung, wenn Sie wissen, dass Ihre Klasse nicht Art von Funktionalität „wenn abwesend setzen“ hat.
  3. Wenn Sie eine Bibliothek entwerfen - dann auf dieser Synchronisierung oder die Methode zu synchronisieren, ist oft klüger. Weil man nur selten in der Lage ist, zu entscheiden, wie Ihre Klasse verwendet werden soll.
  4. Had Vektor verwendete ein eigenes Sperrobjekt - es unmöglich gewesen wäre, richtig „wenn abwesend setzen“ zu bekommen. Der Client-Code wird nie so einen Griff an die privaten Sperre gewinnt die Grundregel mit dem exakt gleichen LOCK bricht seinen Zustand zu schützen.
  5. Synchronisieren auf dieser oder synchronisierten Methoden tun ein Problem haben, wie andere haben darauf hingewiesen - jemand eine Sperre bekommen konnte und sie nie loslassen. Alle anderen Threads halten würde warten, bis die Sperre aufgehoben werden.
  6. So wissen, was Sie tun, und das man annehmen, die richtige ist.
  7. Jemand argumentiert, dass ein eigenes Sperrobjekt mit gibt Ihnen eine bessere Granularität - z.B. wenn zwei Operationen nichts zu tun haben - sie könnten von verschiedenen Schlössern bewacht werden in einen besseren Durchsatz führt. Aber das ist meiner Meinung nach Design Geruch und keinen Code Geruch - wenn zwei Operationen völlig unabhängig sind, warum sind sie Teil der gleichen Klasse? Warum sollte ein Klasse-Club nicht verwandten Funktionalitäten überhaupt? Kann eine Utility-Klasse sein? Hmmmm - einige util String-Manipulation und Kalenderdatumsformatierung durch die gleiche Instanz bereitstellt ?? ... macht keinen Sinn für mich zumindest !!

Nein, das solltest du nicht stets.Ich neige jedoch dazu, dies zu vermeiden, wenn es mehrere Anliegen für ein bestimmtes Objekt gibt, die nur in Bezug auf sich selbst threadsicher sein müssen.Beispielsweise könnten Sie über ein veränderbares Datenobjekt verfügen, das über die Felder „Beschriftung“ und „Übergeordnet“ verfügt.Diese müssen threadsicher sein, aber durch die Änderung des einen muss das Schreiben/Lesen des anderen nicht blockiert werden.(In der Praxis würde ich dies vermeiden, indem ich die Felder als flüchtig deklariere und/oder die AtomicFoo-Wrapper von java.util.concurrent verwende.)

Die Synchronisierung ist im Allgemeinen etwas umständlich, da sie eine große Sperre setzt, anstatt genau darüber nachzudenken, wie Threads umeinander herum arbeiten dürfen.Benutzen synchronized(this) ist sogar noch ungeschickter und asozialer, denn es heißt: „Niemand darf sich ändern.“ irgendetwas in dieser Klasse, während ich die Sperre halte".Wie oft muss man das eigentlich machen?

Ich hätte viel lieber granularere Schlösser;Selbst wenn Sie verhindern möchten, dass sich alles ändert (vielleicht serialisieren Sie das Objekt), können Sie einfach alle Sperren erwerben, um dasselbe zu erreichen. Außerdem ist es auf diese Weise expliziter.Wenn Sie verwenden synchronized(this), ist nicht genau klar, warum Sie synchronisieren oder welche Nebenwirkungen dies haben könnte.Wenn du benutzt synchronized(labelMonitor), oder noch besser labelLock.getWriteLock().lock(), ist klar, was Sie tun und worauf sich die Auswirkungen Ihres kritischen Abschnitts beschränken.

Kurze Antwort . Sie haben den Unterschied zu verstehen und die Wahl auf dem Code abhängig machen

Lange Antwort : Generell würde ich lieber versuchen zu vermeiden synchronisieren (this) Konkurrenz zu reduzieren, aber private Schlösser die Komplexität Sie bewusst sein müssen. So verwenden Sie die richtige Synchronisation für den richtigen Job. Wenn Sie nicht so mit Multithread-Programmierung erfahren werden würde ich eher zu Fall bleiben Sperren und zu diesem Thema nachlesen. (Das heißt: nur mit synchronisieren (this) nicht automatisch Ihre Klasse machen voll Thread-sicher.) Dies ist ein nicht einfaches Thema, aber sobald man sich daran gewöhnt hat, ist die Antwort, ob die Verwendung < em> synchronisieren (this) oder nicht natürlich kommt.

Eine Sperre wird verwendet, entweder für Sichtbarkeit oder für einige Daten von gleichzeitige Änderung schützen , die an Rennen führen kann.

Wenn Sie müssen nur primitive Art Operationen machen sein Atom gibt es Optionen wie AtomicInteger und Ähnliches.

Aber wenn man zwei ganze Zahlen haben, die einander wie x und y Koordinaten verknüpft sind, die miteinander verbunden sind und sollten in einer atomaren Weise verändert werden. Dann würden Sie schützen sie eine gleiche Sperre verwendet wird.

Eine Sperre soll nur den Staat schützen, die miteinander in Beziehung steht. Nicht weniger und nicht mehr. Wenn Sie synchronized(this) in jeder Methode verwenden, dann, selbst wenn der Zustand der Klasse steht in keinem Zusammenhang alle Fäden werden Behauptung sogar gegenüber, wenn nicht verwandten Zustand zu aktualisieren.

class Point{
   private int x;
   private int y;

   public Point(int x, int y){
       this.x = x;
       this.y = y;
   }

   //mutating methods should be guarded by same lock
   public synchronized void changeCoordinates(int x, int y){
       this.x = x;
       this.y = y;
   }
}

Im obigen Beispiel habe ich nur eine Methode, die sowohl x und y und nicht zwei verschiedene Methoden wie x und y mutiert sind ähnlich, und wenn ich x und y zwei verschiedene Methoden gegeben hatte separat dann mutiert wäre es nicht Thread gewesen sicher.

Dieses Beispiel ist nur zu zeigen und nicht unbedingt so, wie es umgesetzt werden soll. Der beste Weg, es zu tun, wäre es machen UNVERäNDERLICH .

Jetzt im Gegensatz zB Point, gibt es ein Beispiel von TwoCounters bereits von @Andreas vorgesehen, wo der Staat, die miteinander nicht verwandt ist durch zwei verschiedene Schlösser als Staat geschützt wird.

Der Prozess verschiedene Schlösser der Verwendung von unabhängigen Staaten zu schützen, heißt Sperren Striping oder Lock-Splitting

Der Grund, nicht zu synchronisieren, auf das ist, dass manchmal braucht man mehr als eine Sperre (die zweite Sperre oft nach einigen zusätzlichen Denken entfernt wird, aber immer noch brauchen es in dem Zwischenzustand). Wenn Sie sperren auf das , müssen Sie immer daran denken, die eine der beiden Schlösser ist das ; Wenn Sie auf ein privates Objekt zu sperren, der Variablenname sagt Ihnen.

Aus der Sicht des Lesers, wenn Sie sehen, Sperren auf das , Sie immer die zwei Fragen beantworten müssen:

  1. , welche Art von Zugriff ist geschützt durch das
  2. ist eine Sperre wirklich genug, nicht jemand einen Fehler einführen?

Ein Beispiel:

class BadObject {
    private Something mStuff;
    synchronized setStuff(Something stuff) {
        mStuff = stuff;
    }
    synchronized getStuff(Something stuff) {
        return mStuff;
    }
    private MyListener myListener = new MyListener() {
        public void onMyEvent(...) {
            setStuff(...);
        }
    }
    synchronized void longOperation(MyListener l) {
        ...
        l.onMyEvent(...);
        ...
    }
}

Wenn zwei Threads longOperation() auf zwei verschiedene Instanzen von BadObject beginnen, sie zu erwerben ihre Schlösser; wenn es Zeit ist l.onMyEvent(...) aufzurufen, haben wir eine Sackgasse, weil weder der Fäden des anderen Objekts Sperre erwerben kann.

In diesem Beispiel haben wir aus der Sackgasse, indem zwei Schlösser, eine für kurze Operationen und eine für lange diejenigen beseitigen kann.

Wie schon gesagt, hier synchronisierten Block benutzerdefinierte Variablen als Sperrobjekt verwenden kann, wenn synchronisierte Funktion nur „der“ verwendet. Und natürlich können Sie mit Bereichen Ihrer Funktion manipulieren, die synchronisiert werden sollen und so weiter.

Aber jeder sagt, dass kein Unterschied zwischen synchronisierten Funktion und Block, der ganze Funktion „dieses“ als Sperrobjekt abdeckt. Das ist nicht wahr, ist der Unterschied in Byte-Code, der in beiden Situationen erzeugt wird. Im Falle einer synchronisierten Blocknutzung sollten lokale Variable, die Referenz zugewiesen werden, um „dieses“ hält. Und als Ergebnis werden wir etwas größere Größe der Funktion (nicht relevant, wenn Sie nur wenige Anzahl von Funktionen haben).

Weitere detaillierte Erläuterung des Unterschiedes finden Sie hier: http://www.artima.com/insidejvm/ed2/threadsynchP.html

Auch Verwendung von synchronisierten Block ist nicht gut, aufgrund folgenden Sicht:

  

Das synchronisierte Schlüsselwort wird in einem Bereich sehr begrenzt: wenn eine synchronisierte Block austritt, werden alle Threads, die für die Sperre warten freigegeben werden müssen, aber nur einer dieser Fäden wird das Schloss zu nehmen; alle anderen sehen, dass das Schloss genommen und zurück in den gesperrten Zustand gehen. Das ist nicht nur eine Menge vergeudeter Verarbeitungszyklen: oft der Kontextwechsel entsperren auch ein Thread Paging-Speicher von der Platte beinhaltet, und das ist sehr, sehr teuer

.

Für weitere Details in diesem Bereich würde ich empfehlen Sie diesen Artikel lesen: http://java.dzone.com/articles/synchronized-considered

Das ist wirklich nur eine Ergänzung zu den anderen Antworten, aber wenn Ihre Haupteinwand private Objekte für Verriegelung ist, dass es mit Feldern Ihre Klassen clutters, das nicht auf die Business-Logik in Zusammenhang steht dann auf Projekt Lombok hat @Synchronized die vorformulierten zur Compile-Zeit zu generieren:

@Synchronized
public int foo() {
    return 0;
}

kompiliert

private final Object $lock = new Object[0];

public int foo() {
    synchronized($lock) {
        return 0;
    }
}

Ein gutes Beispiel für die Anwendung synchronisiert (this).

// add listener
public final synchronized void addListener(IListener l) {listeners.add(l);}
// remove listener
public final synchronized void removeListener(IListener l) {listeners.remove(l);}
// routine that raise events
public void run() {
   // some code here...
   Set ls;
   synchronized(this) {
      ls = listeners.clone();
   }
   for (IListener l : ls) { l.processEvent(event); }
   // some code here...
}

Wie Sie hier sehen können, verwenden wir dies einfach synchronisieren kooperieren von lengthly (möglicherweise unendliche Schleife von run-Methode) mit einigen synchronisierten Methoden gibt.

Natürlich kann es sehr leicht mit der Verwendung von synchronisierten auf einem privaten Feld neu geschrieben werden. Aber manchmal, wenn wir bereits einige Designs mit synchronisierten Methoden haben (das heißt Legacy-Klasse, leiten wir aus, synchronisiert (das) die einzige Lösung sein).

Es hängt von der Aufgabe, die Sie tun wollen, aber ich würde es nicht verwenden. Überprüfen Sie auch, ob der Thread-save-ness Sie accompish wollen nicht synchronisieren (this) an erster Stelle getan werden könnte? Es gibt auch einige schöne Schlösser in der API das könnte Ihnen helfen:)

Ich möchte nur eine mögliche Lösung für einzigartige private Referenzen in Atom-Teilen des Codes ohne Abhängigkeiten erwähnen. Sie können eine statische HashMap mit Schlössern und eine einfache statische Methode namens Atom () verwenden, die automatisch mit Stapelinformation (vollständige Klassennamen und die Zeilennummer) erforderliche Referenzen erstellt. Dann können Sie diese Methode in synchronisieren Anweisungen verwenden, ohne neues Sperrobjekt schreiben.

// Synchronization objects (locks)
private static HashMap<String, Object> locks = new HashMap<String, Object>();
// Simple method
private static Object atomic() {
    StackTraceElement [] stack = Thread.currentThread().getStackTrace(); // get execution point 
    StackTraceElement exepoint = stack[2];
    // creates unique key from class name and line number using execution point
    String key = String.format("%s#%d", exepoint.getClassName(), exepoint.getLineNumber()); 
    Object lock = locks.get(key); // use old or create new lock
    if (lock == null) {
        lock = new Object();
        locks.put(key, lock);
    }
    return lock; // return reference to lock
}
// Synchronized code
void dosomething1() {
    // start commands
    synchronized (atomic()) {
        // atomic commands 1
        ...
    }
    // other command
}
// Synchronized code
void dosomething2() {
    // start commands
    synchronized (atomic()) {
        // atomic commands 2
        ...
    }
    // other command
}

Vermeiden synchronized(this) als Verriegelungsmechanismus verwendet: Diese die ganze Klasse Instanz Sperren und Deadlocks verursachen kann. In solchen Fällen den Code Refactoring nur eine bestimmte Methode oder Variable zu sperren, auf diese Weise ganze Klasse bekommt nicht gesperrt. Synchronised kann innerhalb Methode Ebene verwendet werden.
Statt synchronized(this) zu verwenden, zeigt Code unten, wie Sie nur eine Methode sperren könnte.

   public void foo() {
if(operation = null) {
    synchronized(foo) { 
if (operation == null) {
 // enter your code that this method has to handle...
          }
        }
      }
    }

Mein zwei Cent im Jahr 2019, obwohl diese Frage bereits abgerechnet worden sein könnte.

Das Ankoppeln ‚this‘ ist nicht schlecht, wenn Sie wissen, was Sie tun, aber hinter den Kulissen Verriegelung auf ‚this‘ ist (was leider, was synchronisiert Schlüsselwort in Methodendefinition erlaubt).

Wenn Sie wirklich möchten, dass Benutzer Ihrer Klasse in der Lage sein, zu ‚stehlen‘ Ihr Schloss (das heißt verhindern, dass andere Threads mit ihm zu tun), Sie wollen eigentlich alle synchronisierten Methoden warten, während andere Synchronisierungsmethode ausgeführt wird und so weiter. Es sollte vorsätzliche und gut durchdachte ab (und damit versteht es Ihren Anwender dokumentiert helfen) sein.

Zum weiteren erarbeiten, in der Rückseite, müssen Sie wissen, was Sie ‚gewinnen‘ (oder ‚verlieren‘ auf out), wenn Sie auf eine nicht zugängliche Schloss Schloss (niemand kann ‚stehlen‘ Ihr Schloss, sind Sie in der totalen Kontrolle und bald...).

Das Problem für mich ist, dass synchronisierte Schlüsselwort in der Methodendefinition Signatur macht es für Programmierer einfach zu leicht nicht zu denken, über das, was auf das sperren eine mächtige wichtige Sache ist, don wäre es, wenn Sie zu denken, ‚t wollen Probleme in einem Multi-threaded-Programm auszuführen.

Man kann nicht behaupten, dass ‚typisch‘ Sie wollen nicht, Benutzer Ihrer Klasse diese Dinge oder dass in der Lage sein ‚typisch‘ zu tun, Sie wollen ... Es hängt davon ab, welche Funktionen Sie Codierung. Sie können keine Faustregel, wie Sie nicht alle Anwendungsfälle vorhersagen kann.

Betrachten Sie zum Beispiel die Printwriter, die eine interne Sperre verwendet, aber dann kämpfen die Menschen es von mehreren Threads verwenden, wenn sie ihre Leistung nicht wollen, verschachteln.

Sollte Ihr Schloss außerhalb der Klasse zugänglich sein oder nicht, ist Ihre Entscheidung als Programmierer auf der Grundlage dessen, was Funktionalität der Klasse. Es ist Teil der api. Sie können beispielsweise nicht wegbewegen von synchronisiert (this) zu synchronisieren (provateObjet), ohne zu brechen Änderungen im Code zu riskieren, es zu benutzen.

Anmerkung 1: Ich weiß, Sie können, was synchronisiert erreichen (dies) ‚erreicht‘ durch ein explizites Sperrobjekt verwenden und setzen Sie sich, aber ich denke, dass es nicht notwendig ist, wenn Ihr Verhalten gut dokumentiert ist und Sie wissen wirklich, was Verriegelung auf ‚this‘ bedeutet.

Hinweis 2: Ich stimme nicht mit dem Argument, dass, wenn ein Code versehentlich Sperre seine Fehler ist Diebstahl, und Sie haben es zu lösen. Dies in einer Art und Weise ist das gleiche Argument wie sagen, ich kann sogar alle meine Methoden öffentlich machen, wenn sie nicht öffentlich sein sollen. Wenn jemand aus Versehen 'ruft meinen beabsichtigt ist seine private Methode ein Fehler zu sein. Warum diesen Unfall in erster Linie ermöglichen !!! Wenn Fähigkeit, Ihre Sperre zu stehlen ein Problem für Ihre Klasse ist erlaubt es nicht. So einfach wie das.

Ich denke Punkte ein (jemand anderes mit Ihrem Schloss) und zwei (alle Methoden die gleiche Sperre unnötig verwendet wird) in jeder ziemlich große Anwendung passieren kann. Vor allem, wenn es keine gute Kommunikation zwischen den Entwicklern.

Es ist nicht in Stein gemeißelt, es ist vor allem eine Frage der guten Praxis und verhindert Fehler.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top