10 Stimmen

Ist eine Synchronisierung beim Lesen erforderlich, wenn keine Konflikte auftreten können?

Beachten Sie den nachstehenden Code Sniper:

package sync;

public class LockQuestion {
    private String mutable;

    public synchronized void setMutable(String mutable) {
        this.mutable = mutable;
    }

    public String getMutable() {
        return mutable;
    }   
}

Zum Zeitpunkt Time1 wird Thread1 die Variable "mutable" aktualisieren. Die Synchronisierung ist im Setter erforderlich, um den Speicher vom lokalen Cache in den Hauptspeicher zu leeren. Zum Zeitpunkt Time2 (Time2 > Time1, keine Thread-Konkurrenz) liest Thread Thread2 den Wert der Variable mutable.

Frage ist - muss ich synchronized vor getter setzen? Sieht aus wie dies wird keine Probleme verursachen - Speicher sollte aktuell sein und Thread2's lokalen Cache-Speicher sollte ungültig & aktualisiert von Thread1, aber ich bin nicht sicher.

9 Stimmen

Threading in Java ist definiert als passiert-vor Beziehungen. Versuchen Sie nicht, in Begriffen wie "flushing caches" zu denken, denn das wäre falsch. Zum einen sind die Optimierungen des Compilers wichtig. Selbst wenn das Flushing von Caches ein genaues Modell wäre, wäre die Reihenfolge der Aktualisierungen nicht garantiert, wenn das verwiesene Objekt veränderbar ist. (Bis zur Spezifikation 1.5 verwendete die Spezifikation ein Flushing-Caches-Modell, das jedoch nicht implementierbar war).

4voto

Anon Punkte 2636

Anstatt sich zu fragen, warum man nicht einfach die atomaren Referenzen in java.util.concurrent ?

(und falls es etwas wert ist, meine Lektüre von passiert-vor garantiert nicht, dass Thread2 Änderungen an mutable sieht, es sei denn, er verwendet auch synchronized ... aber dieser Teil der JLS bereitet mir immer Kopfschmerzen, also verwende ich die atomaren Referenzen)

0 Stimmen

Atomic-Klassen sind zwar sehr nützlich, haben aber zwangsläufig einen kleinen Leistungsnachteil. Ich ziehe es vor, "einfache" Typen zu verwenden, es sei denn, die Atomizität der Aktualisierung ist erforderlich. Im obigen Fall ist nur die Sichtbarkeit der Aktualisierung erforderlich.

0 Stimmen

@Petro Semeniuk Ohne eines der atomaren Objekte, Synchronisation und/oder Volatile gibt es keine Möglichkeit, die "Sichtbarkeit" von Update und Read im Verhältnis zueinander zu garantieren. Das kann eine Rolle spielen oder auch nicht.

0 Stimmen

Beachten Sie, dass die synchronized / volatile muss sich auf dasselbe Objekt beziehen.

4voto

Matt Punkte 7991

Es ist in Ordnung, wenn Sie mutable flüchtig machen, Details in der " billige Schreib-Lese-Sperre "

0 Stimmen

Danke! Ich denke über flüchtige Klassen ähnlich wie über Atomic*-Klassen - sie sind billig, aber sie sind nicht kostenlos. Idealerweise möchte ich erzwingen, "passiert-vor", aber ohne jede Strafe/Sperre auf Lesungen.

0 Stimmen

@Petro Semeniuk Es geht nicht darum, "frei" zu sein, sondern darum, "immer die gewünschte Semantik zu haben" (z.B. keinen seltsamen Synchronisationsfehler eintreten zu lassen). Wie auch immer, Ihr Programm auf diese Weise ohne "teure Sperre" (die, seien wir ehrlich, verdammt billig ist, wenn sie nicht stark beansprucht wird) gut funktionieren könnte - aber das ist nur für Sie im Gesamtbild zu analysieren. Es ist besser, einfach "das Richtige" zu tun und sich nicht um alberne Mikro-Optimierungen zu kümmern.

0 Stimmen

Obwohl in der Praxis volatile ist selten nützlich.

1voto

SpeksETC Punkte 973

Sind Sie absolut sicher, dass der Getter erst aufgerufen wird, nachdem der Setter aufgerufen wurde? Wenn ja, brauchen Sie den Getter nicht zu synchronisieren, da gleichzeitige Lesevorgänge nicht synchronisiert werden müssen.

Wenn die Möglichkeit besteht, dass get und set gleichzeitig aufgerufen werden können, müssen Sie die beiden definitiv synchronisieren.

2 Stimmen

Das ist nicht wahr. Ohne eine synchronisierte Sperre im Getter gibt es keine "happens-before"-Garantie. Der Setter könnte ausgelöst, beendet und dann der Getter in einem anderen Thread ausgelöst werden. sondern liest den alten Wert. Dies kann von Bedeutung sein, muss es aber nicht. Für einen Einzelzugriff, volatile wäre hier "gleichwertig", lässt aber die größeren Auswirkungen der Synchronisierung außer Acht.

0 Stimmen

OP schrieb, dass der Setter bei Time1 aufgerufen wird, Getter wird bei Time2 aufgerufen, und Time2 > Time1, so ist es möglich, dass Thread 2 nur gestartet wird, nachdem Thread 1 abgeschlossen ist, was eine "happens-before"-Garantie gibt (z. B. thread1.Start(), thread1.Join(), thread2.Start())

2 Stimmen

Ich sehe nichts in Bezug auf das Anhalten oder Beitreten von Themen. T2 > T1 ist nicht ausreichend für happens-before - Caching wirft diese Annahme über den Haufen.

1voto

Zan Lynx Punkte 51045

Wenn Sie sich so sehr um die Leistung im Lesethread sorgen, dann lesen Sie den Wert nur einmal mit der richtigen Synchronisierung oder flüchtigen oder atomaren Referenzen. Dann weisen Sie den Wert einer einfachen alten Variablen zu.

Die Zuweisung an die einfache Variable erfolgt garantiert nach dem atomaren Lesen (denn wie sonst könnte sie den Wert erhalten?), und wenn der Wert nie wieder von einem anderen Thread geschrieben wird, ist alles in Ordnung.

1voto

Peter Lawrey Punkte 511323

Ich denke, Sie sollten mit etwas beginnen, das korrekt ist, und später optimieren, wenn Sie wissen, dass Sie ein Problem haben. Ich würde einfach AtomicReference verwenden, es sei denn, ein paar Nanosekunden sind zu lang ;)

public static void main(String... args) {
    AtomicReference<String> ars = new AtomicReference<String>();
    ars.set("hello");
    long start = System.nanoTime();
    int runs = 1000* 1000 * 1000;
    int length = test(ars, runs);
    long time = System.nanoTime() - start;
    System.out.printf("get() costs " + 1000*time / runs + " ps.");
}

private static int test(AtomicReference<String> ars, int runs) {
    int len = 0;
    for (int i = 0; i < runs; i++)
        len = ars.get().length();
    return len;
}

Drucke

get() costs 1219 ps.

ps ist eine Pikosekunde, mit ist 1 Millionstel einer Mikrosekunde.

0 Stimmen

Vielen Dank für die Bereitstellung von Perf-Metriken. Btw, wenn Sie das gleiche mit einfachem String tun, werden Sie sehen, dass der ständige Zugriff auf AtomicReference die Kosten des Abrufs verdoppelt. Wahrscheinlich ist der effizienteste Weg, um es zu tun ist, um Wert auf lokale Variable (innerhalb Testmethode) zuweisen

0 Stimmen

Das Get ist nur ein flüchtiges Lesen, das Deklarieren als flüchtig hat denselben Effekt.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X