Wie synchronisiere oder sperre ich Variablen in Java?

77

Lassen Sie mich dieses kleine und einfache Beispiel verwenden:

class Sample {
    private String msg = null;

    public void newmsg(String x){
        msg = x;
    }

    public String getmsg(){
        String temp = msg;
        msg = null;
        return temp;
    }
}

Nehmen wir an, die Funktion newmsg()wird von anderen Threads aufgerufen, auf die ich keinen Zugriff habe.

Ich möchte die Synchonisierungsmethode verwenden, um sicherzustellen, dass die Zeichenfolge msgnur von einer Funktion pro Zeit verwendet wird. Mit anderen Worten, die Funktion newmsg()kann nicht gleichzeitig mit ausgeführt werden getmsg().

Winter
quelle
4
Fragen Sie, wie Sie das Schlüsselwort "synchronized" in Java verwenden können? Eine einfache Google-Suche kommt mit vielen nützlichen Treffern zurück, einschließlich dieser einen download.oracle.com/javase/tutorial/essential/concurrency/…
Kal
4
Übrigens wäre es viel besser, unsere Methode getmsg () so etwas wie popmsg () oder Consumemsg () zu nennen, da sie die Klasse modifiziert
Naumcho

Antworten:

170

Das ist ziemlich einfach:

class Sample {
    private String message = null;
    private final Object lock = new Object();

    public void newMessage(String x) {
        synchronized (lock) {
            message = x;
        }
    }

    public String getMessage() {
        synchronized (lock) {
            String temp = message;
            message = null;
            return temp;
        }
    }
}

Beachten Sie, dass ich nicht entweder die Methoden machen sich synchronisiert oder synchronisieren auf this. Ich bin der festen Überzeugung, dass es eine gute Idee ist, Sperren nur für Objekte zu erwerben, auf die nur Ihr Code Zugriff hat, es sei denn, Sie legen die Sperre absichtlich offen. Es macht es viel einfacher, sich zu vergewissern, dass nichts anderes Sperren in einer anderen Reihenfolge als Ihr Code usw. erwerben wird.

Jon Skeet
quelle
8
Was ist der Unterschied zwischen dem Einstellen der Synchronisierungsfunktion oder dem Erstellen dieser Sperre?
Winter
3
@Giacomo: Ich möchte, dass mein Code lesbar und so gestaltet ist, dass ich darüber nachdenken kann. Das ist doppelt wichtig, wenn es um Multithreading geht. Es ist nicht übertrieben zu sagen, dass dies die Art von Code ist, die ich fast immer verwenden würde. Was Verschwendung angeht ... wie oft würden Sie erwarten, dass diese Verschwendung erheblich ist?
Jon Skeet
3
@ Giacomo: Der Punkt ist, dass es offensichtlich ist, dass Sie sich auf etwas festlegen, was sonst niemand kann. Das scheint ziemlich einfach zu sein: Sie lesen den Code und sind fertig. Betrachten Sie nun Ihr Beispiel: Woher wissen Sie, was auf Ihrem Objekt synchronisiert werden könnte ? Wie können Sie das Sperrverhalten verstehen? Sie müssen den gesamten Code lesen . Ich würde daher sagen, dass es weniger lesbar ist, wenn Sie tatsächlich von dem korrekten Verhalten gestört werden. Ehrlich gesagt glaube ich, dass es ein Fehler war, alle Objekte in Java sperren zu lassen. (Ein Fehler, den .NET leider kopiert hat.)
Jon Skeet
3
Die synchronisierte @ Winter-Methode blockiert "dies", also das Beispielobjekt selbst. Aber synchronisierter Block auf Objekt "sperren" blockiert "sperren", nicht das Beispiel
Jemshit Iskenderov
2
@PhilipRego: Erstens, weil sich dann der Wert, auf den gesperrt wird, ändern würde, was die Dinge in vielerlei Hinsicht unsicher macht. Zweitens, weil dies leicht bedeuten könnte, dass Sperren unbeabsichtigt von mehreren Objekten gemeinsam genutzt werden, was zu allen möglichen anderen Problemen führt. Drittens: Trennung von Bedenken. Ich bin sehr interessiert an einem Feld, das einen Zweck hat.
Jon Skeet
53

Für diese Funktionalität ist es besser, überhaupt kein Schloss zu verwenden. Versuchen Sie es mit einer AtomicReference.

public class Sample {
    private final AtomicReference<String> msg = new AtomicReference<String>();

    public void setMsg(String x) {
        msg.set(x);
    }

    public String getMsg() {
        return msg.getAndSet(null);
    }
}

Keine Sperren erforderlich und der Code ist meiner Meinung nach einfacher. In jedem Fall wird ein Standardkonstrukt verwendet, das das tut, was Sie wollen.

Peter Lawrey
quelle
9
Zu java.util.concurrent.atomicAtomicBooleanAtomicInteger
Ihrer Information,
10

Ab Java 1.5 ist es immer eine gute Idee, das Paket java.util.concurrent in Betracht zu ziehen. Sie sind derzeit der neueste Verriegelungsmechanismus in Java. Der Synchronisierungsmechanismus ist schwerer als die Klassen java.util.concurrent.

Das Beispiel würde ungefähr so ​​aussehen:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Sample {

    private final Lock lock = new ReentrantLock();

    private String message = null;

    public void newmsg(String msg) {
        lock.lock();
        try {
            message = msg;
        } finally {
            lock.unlock();
        }
    }

    public String getmsg() {
        lock.lock();
        try {
            String temp = message;
            message = null;
            return temp;
        } finally {
            lock.unlock();
        }
    }
}
Niclas Meier
quelle
1
Gemäß den Dokumenten sollte die Sperre vor dem Try-Block erfolgen.
Aaron Roller
3

In diesem einfachen Beispiel können Sie in beiden Methodensignaturen einfach synchronizedals Modifikator nach setzen public.

Komplexere Szenarien erfordern andere Dinge.

gd1
quelle
3

Verwenden Sie das synchronizedSchlüsselwort.

class sample {
    private String msg=null;

    public synchronized void newmsg(String x){
        msg=x;
    }

    public synchronized string getmsg(){
        String temp=msg;
        msg=null;
        return msg;
    }
}

Die Verwendung des synchronizedSchlüsselworts für die Methoden erfordert Threads, um eine Sperre für die Instanz von zu erhalten sample. Wenn sich also ein Thread in befindet newmsg(), kann kein anderer Thread die Instanz von sperren sample, selbst wenn er versucht, sie aufzurufen getmsg().

Auf der anderen Seite kann die Verwendung von synchronizedMethoden zu einem Engpass werden, wenn Ihre Methoden lang laufende Vorgänge ausführen. Alle Threads müssen warten, auch wenn sie andere Methoden in diesem Objekt aufrufen möchten, die verschachtelt werden könnten.

IMO, in Ihrem einfachen Beispiel ist es in Ordnung, synchronisierte Methoden zu verwenden, da Sie tatsächlich zwei Methoden haben, die nicht verschachtelt werden sollten. Unter anderen Umständen kann es jedoch sinnvoller sein, ein Sperrobjekt zum Synchronisieren zu haben, wie in der Antwort von Joh Skeet gezeigt.

no.good.at.coding
quelle
2

Wenn Sie bei einer anderen Gelegenheit eine Sammlung anstelle eines Strings synchronisieren, iterieren Sie möglicherweise über die Sammlung und befürchten, dass sie mutiert, bietet Java 5 Folgendes:

SK9
quelle