threads

Threads


Motivation

  • langwierige Operationen
  • warten auf IO
  • modellieren paralleler Abläufe

Warten auf IO

Szenario
Mehrere Clients connecten zu einem Server, um Daten zu empfangen. Ohne Concurrency könnte sich gleichzeitig nur ein einziger Client verbinden.
Lösung
Jedem Client wird ein Thread zugeteilt, welcher sich um die Kommunikation kümmert.

Einbettung im OS
  • moderne Prozessoren verfügen über mehrere CPUs/Cores
  • ein Prozess kann Threads erzeugen
  • der OS-Scheduler verwaltet, welcher Prozess bzw. Thread wann arbeiten darf
  • ein Thread kann jederzeit vom OS pausiert werden ⚠️

Time Slicing

time-slicing Threads > Prozessoren


Hyperthreading

hyperthreading


Erzeugung

Thread thread = new Thread();
thread.start();

Festlegung des auszuführenden Codes

  • Thread extenden
  • Runnable implementieren

Thread extenden

public class MyThread extends Thread {
	@Override
	public void run(){
		System.out.println("MyThread running");
	}
}
MyThread myThread = new MyThread();
myThread.start();

Runnable implementieren

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("MyRunnable running");
    }
}
Runnable runnable = new MyRunnable();
new Thread(runnable).start();
runnable = () -> foo();
new Thread(runnable, "name").start();
👍
  • Interfaces sind dazu da, Verhalten zu deklarieren
  • Möglichkeit, andere Klassen zu extenden

Beispiel

public class ThreadExample {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
        for (int i = 0; i < 4; i++) {
            new Thread("" + i) {
                @Override
                public void run() {
                    System.out.println("Thread: " + getName() + " running");
                }
            }.start();
        }
    }
}
main
Thread: 1 running
Thread: 2 running
Thread: 0 running
Thread: 3 running

Fehler


Runnable runnable = () -> foo();
new Thread(runnable).run();
new Thread(runnable).start();

run läuft im Mainthread


Meta

Interfaces trennen das was vom wie
Threads trennen das was vom wann


Kontrolle


Thread thread = Thread.currentThread();
 
Liefert den aktuellen Thread

Thread.sleep(1000); //1000ms
 

Pausiert den aktuellen Thread

Zum Stoppen muss run enden


InterruptedException

Thread.currentThread().interrupt();
  • wird von vielen Thread-Methoden geworfen
  • unterbricht einen laufenden Thread
  • im unterbrochenen Thread wird eine InterruptedException ausgelöst
  • wie der Thread darauf reagiert, wird im catch definiert

  • join

    Thread thread = new Thread(() -> {
        sleep(1000);
        System.out.println("guaranteed");
    });
    thread.start();
    thread.join();
    System.out.println("order");

    Wenn Thread A während seiner Ausführung B.join aufruft, dann wartet A mit seiner weiteren Ausführung, bis B dead ist.


    Daemon Threads

    thread.setDaemon(true);
    • in UNIX ein Hintergrundprozess, in Windows genannt Service
    • wenn nur mehr Daemon Threads laufen, beendet sich die JVM ⟹ Hintergrundaufgaben
    • muss vor dem Starten gesetzt sein

    Synchronisation

    public class IdGenerator {
        int lastUsedId;
    
        public int incrementValue() {
            return ++lastUsedId;
        }
    }
    • ein Thread ✔️
    • zwei Threads mit demselben IdGenerator:
      • A kriegt 1, B kriegt 2, lastUsedId ist 2
      • A kriegt 2, B kriegt 1, lastUsedId ist 2
      • A kriegt 1, B kriegt 1, lastUsedId ist 1 ❌

    Der dritte Fall ist selten (1:20000), aber er passiert


    Atomare Operationen

    • ganz oder gar nicht
    • auf Datenbanken Transaktion
    • in Java:
    • Variablen lesen (außer long/double)
    • Variable schreiben (außer long/double)
    • im Package java.util.concurrent.atomic finden sich Klassen wie AtomicInteger mit hilfreichen Methoden

    synchronized Methoden

    public synchronized int incrementValue() {
        return ++lastUsedId;
    }
    • erzeugt einen Mutex (MUTual EXclusion)
    • nur ein Thread kann diesen Block gleichzeitig betreten

    synchronized Blöcke

    public void foo() {
        // code
        synchronized(object) {
            // code
        }
    }
    • nur ein Thread kann pro Objekt das Lock / den Monitor halten
    • bevor ein Thread den Block betreten darf, muss er das Lock requesten
      • bekommt er das Lock, fährt der Thread fort
      • ansonsten blockt der Thread und wartet
    • ein Thread kann dasselbe Lock mehrmals halten

    Locking

    synchronized


    Methoden Locks

    eine synchronized Methode locked

    • this
    • XXX.class bei statischen Methoden
    • 👍 Alternative: gut benanntes Lock-Objekt

    Lifecycle

    Lifecycle


    Busy waiting

    public class MySignal {
        private boolean hasDataToProcess = false;
    
        public synchronized boolean hasDataToProcess() {
            return this.hasDataToProcess;
        }

    Thread A übergibt Daten, Thread B wartet:

    private MySignal sharedSignal;
    
    while(!sharedSignal.hasDataToProcess()){
      // do nothing... busy waiting
    }
    sharedSignal.processData();

    wait/notify

    synchronized(elevator) {
        while(burning)
            elevator.wait();
    }
    synchronized(elevator) {
        extinguishFire();
        elevator.notifyAll();
    }

    wait

    elevator.wait();
    • Object-Methode
    • muss in synchronized-Block gecalled werden, sonst IllegalMonitorStateException 💥
    • Thread geht in den Zustand blocked
    • Thread gibt alle Locks frei
    • immer in einem while
      • mehrere Threads callen wait
      • Spurious Wakeups 👻

    notify/notifyAll

    elevator.notify();
    • Object-Methode
    • muss in synchronized-Block gecalled werden, sonst IllegalMonitorStateException 💥
    • holt einen/alle Threads aus dem Zustand waiting, diese müssen nun wieder das Lock requesten
    • gehaltene Locks werden nicht freigegeben

    private boolean hasDataToProcess = false;
    private MonitorObject monitor = new MonitorObject();
    
    public void setData(Object data) throws InterruptedException {
        synchronized (monitor) {
            while (hasDataToProcess) 
                monitor.wait();
            hasDataToProcess = true;
            monitor.notifyAll();
        }
    }
    
    public void processData() throws InterruptedException {
        synchronized (monitor) {
            while (!hasDataToProcess) 
                monitor.wait();
            doStuff(dataToProcess);
            hasDataToProcess = false;
            monitor.notifyAll();
        }
    }

    Deadlock

    • Website connected zu zwei Datenbanken
    • n mögliche Connections je Db
    • Operation A verbindet sich zu Db1, dann Db2
    • Operation B verbindet sich zu Db2, dann Db1
    • n User führen A aus
    • n User führen B aus
    • alle A-User warten darauf, dass ein B-User seine Connection hergibt
    • die warten allerdings alle auf eine Connection der A-User

    weitere Probleme

    Starvation
    Thread bekommt nie alle Resourcen, um seine Arbeit zu beenden
    Livelock
    wie eine Deadlock, aber Threads sind busy waiting

    Best practice

    • so wenig Abhängigkeiten wie möglich
    • so wenig synchronized wie möglich
    • testen, testen, testen
    • Serverseitig synchronisieren (kapseln)