Atomic-Variablen: Konkurrierender Zugriff auf Daten ohne gegenseitigen Ausschluss

Parallele Programmierung (3IB)

Prof. Dr.-Ing. Sandro Leuchter
Hochschule Mannheim, Fakultät für Informatik
Wintersemester 2024/2025

 

Dieses Werk ist lizenziert unter einer Creative Commons „Namensnennung – Nicht-kommerziell – Weitergabe unter gleichen Bedingungen 4.0 International“ Lizenz.

Überblick

=> Grundlage: Hettel und Tran (2016, Kap. 7)

  • Test/Musterlösung
  • atomarer Zugriff auf Variablen in Java
    • Transaktionen
  • Atomics
    • compare-and-set unabhängig von Java
    • das Java-Package java.util.concurrent.atomic: atomare Skalare (AtomicInteger, AtomicLong, AtomicBoolean), atomare Double und Float, beliebige atomare Funktionen

Atomarer Zugriff auf Variablen

Welche Zugriffe auf Variablen sind atomar, welche nicht?

  • Lesen/Schreiben von Variablen
    • bei primitiven Datentypen außer long und double atomar
    • bei volatile immer atomar
    • Variablen mit Verweisen auf Objekte immer atomar
  • Bei atomarem Zugriff wird der Wert nie inkonsistent.
  • Lesen während anderer Thread ändert:
    • Ergebnis ist Wert vor Änderung oder nach Änderung.

Increment/Decrement sind nicht atomar!

Obwohl sie wie (atomare) Instruktionen aussehen mögen, sind Increment (++-Operator) und Decrement (---Operator) keine atomaren Instruktionen.

  • Es handelt sich um Transaktionen.
  • Synchronisation ist erforderlich.
class SyncCounter {
    private int c = 0;
    public synchronized void inc() {
        c++;
    }
    public synchronized void dec() {
        c--;
    }
    public synchronized int get() {
        return c;
    }
}

Disassemblierte Methode inc():

  public synchronized void inc();
    Code:
       0: aload_0
       1: dup
       2: getfield #12 // Field c:I
       5: iconst_1
       6: iadd
       7: putfield #12 // Field c:I
      10: return

recht ineffizient (mglw. hoher Anteil an gegenseitigem Ausschluss)

Atomics generell und in Java

Compare-and-Swap (Vergleichen und Vertauschen) hier: Compare-and-Set (Vergleichen und Speichern)

Compare-and-Swap ist eine atomare Instruktion, die von den meisten Rechnerarchitekturen bereitgestellt wird.

function cas(p: *int, old: int, new: int):
    if *p <> old
        return false
    *p := new
    return true

Threadsicheres Inkrement mit cas ohne Erfordernis für gegenseitigen Ausschluss:

int tmp = counter
while (! cas(counter, tmp, tmp+1))
    tmp = counter;

Java-Schnittstelle für CAS-Funktionalität: AtomicBoolean, AtomicInteger, AtomicLong

Konstruktoren

AtomicBoolean() AtomicInteger() AtomicLong()
AtomicBoolean/Integer/Long(boolean/int/long initialValue)

Compare-And-Set (atomar)

public final boolean compareAndSet(boolean/int/long expect,
                                   boolean/int/long update)

Lesen/Schreiben (alle atomar)

public final boolean/int/long get()
public final void set(boolean/int/long newValue)
public final boolean/int/long getAndSet(boolean/int/long newValue)
  • liefert den alten Wert zurück

Atomare double und float

gibt es nicht: “You can also hold floats using Float.floatToIntBits and Float.intBitstoFloat conversions, and doubles using Double.doubleToLongBits and Double.longBitsToDouble conversions.”1

public AtomicFloat(float initialValue) {
    this.bits = new AtomicInteger(Float.floatToIntBits(initialValue));
}
public final boolean compareAndSet(float expect, float update) {
    return this.bits.compareAndSet(Float.floatToIntBits(expect),
                                   Float.floatToIntBits(update));
    }
public final void set(float newValue) {
    this.bits.set(Float.floatToIntBits(newValue));
}
public final float get() {
    return Float.intBitsToFloat(this.bits.get());
}

Beliebige atomare Funktion in Atomic*: updateAndGet

public final long updateAndGet(LongUnaryOperator updateFunction)
public final int  updateAndGet(IntUnaryOperator  updateFunction)

@FunctionalInterface
public interface LongUnaryOperator {
    long applyAsLong(long operand);
}

@FunctionalInterface
public interface IntUnaryOperator {
    int applyAsInt(int operand);
}

var i = new AtomicInteger(4711);
i.updateAndGet((in) -> (in * 2));
var l = new AtomicLong(4711);
l.updateAndGet((in) -> (in * 2));

Laboraufgabe “Counter mit Atomics threadsicher machen”

  • Projekt: pp.05.01-CounterAtomic
  • Bearbeitungszeit: 20 Minuten
  • Musterlösung: 10 Minuten
  • Kompatibilität: mindestens Java SE 10

Referenzen

Hettel, Jörg und Manh Tien Tran. 2016. Nebenläufige Programmierung mit Java. Konzepte und Programmiermodelle für Multicore-Systeme. Heildelberg: dpunkt.verlag.