Deklarieren Sie Ihre Variable als volatile
und Sie sind fertig.
... Aber es ist in einigen Fällen möglich, den Zugriff auf Ihre Variable zu optimieren. Sie möchten 100% sicher sein, dass keine anderen Variablen die Cache-Zeile mit Ihrer Variablen teilen. Warum? Wenn keine Freigabe erfolgt, ist die Kopie der neuesten Version der Variablen in CPU-Caches aller Cores im S (gemeinsam genutzten) Zustand verfügbar. Das Lesen dieser Variablen ist schnell, als ob sie nicht als volatile
deklariert wäre. Wenn jedoch andere Daten Cache-Zeile mit Ihrer Variablen teilen und diese anderen Daten häufig geändert werden (zB jede Mikrosekunde), dann werden Threads, die Ihre Variable lesen wollen, blockiert, während der letzte Wert Ihrer Variablen aus dem Cache der CPU übertragen wird, von dem dies geschieht Weitere Daten wurden aktualisiert.
Um oben genannten Szenarien, hier das Programm zu zeigen:
import java.util.concurrent.TimeUnit;
public class CacheLineSharing {
static class Holder {
volatile long pad0;
volatile long pad1;
volatile long pad2;
volatile long pad3;
volatile long pad4;
volatile long pad5;
volatile long pad6; // either pad6 or pad7 shares cache line with x
volatile long x = INIT_VALUE;
volatile long pad7;
volatile long pad8;
volatile long pad9;
volatile long pad10;
volatile long pad11;
volatile long pad12;
volatile long pad13;
volatile long pad14; // definitely in different cache line than x
}
static final int INIT_VALUE = 0;
static final int START_VALUE = 1;
static final int FINISH_VALUE = 2;
static class MyThread extends Thread {
Holder holder;
MyThread(Holder holder) {
this.holder = holder;
}
@Override
public void run() {
while (holder.x != START_VALUE);
long cycles = 0;
while (holder.x != FINISH_VALUE) {
cycles++;
}
System.out.println(String.format("cycles=%d", cycles));
}
}
public static void main(String[] args) throws Exception {
Holder holder = new Holder();
for (int i = 0; i < 4; i++) {
new MyThread(holder).start();
}
long ts = System.nanoTime();
holder.x = START_VALUE;
for (int i = 0; i < 1000000000; i++) {
//holder.pad6 = i;
holder.pad14 = i;
}
holder.x = FINISH_VALUE;
long nanoes = System.nanoTime() - ts;
System.out.println(String.format("millis=%d", TimeUnit.NANOSECONDS.toMillis(nanoes)));
}
}
Unsere Zielgröße ist x
und wir messen, wie schnell das Lesen sein Wert ist. Im ersten Fall wir pad6
Variable (es hat 6 in 7 Chancen des Teilen Cache-Zeile mit x
) ständig aktualisieren:
millis=4317
cycles=584637722
cycles=655006968
cycles=1214910177
cycles=1133123641
~ 280K Iteration pro Sekunde für schnellsten Thread
nun das gleiche Programm-Updates pad14
Variable, die Cache-Zeile mit x
(modernem Intel-CPUs hat Cache-Zeilen von 64 Bytes = 8 Java-long-Positionen) nicht teilt:
cycles=1857323945
cycles=2034309531
cycles=1820202891
millis=1363
cycles=2083430201
~ 1335K Iterationen pro Sekunde auch für slowe st Gewinde.
4,75-mal schneller!
Also, wenn Sie total verrückt nach Optimierung sind, pad Ihre Variable 7 Longs vor und 7 Longs nach.
Setzen Sie die Zuweisung in einen "synchronisierten" Block. –
@AndyTurner Und ist das effizienter für Lesevorgänge? –
Die Lesevorgänge sind nicht im synchronisierten Block. Nur die Zuordnung. Sie müssten den Monitor entsprechend auswählen. –