Upload
others
View
1
Download
0
Embed Size (px)
Citation preview
SYSTEM SOFTWARE 1
Multi
Multithreading
SYSTEM SOFTWARE 2
Multithreading
Einleitung und Grundlagen
Multithreading Grundlagen
Probleme bei Multithreading
Klassisches Modell
Synchronisation
Threads und Swing
Neues Modell
Executors und Futures
Synchronisation
Concurrent Collections
Fallbeispiel
Zusammenfassung
SYSTEM SOFTWARE 4
Klassendiagramm (Auszug)
+run()
«Schnittstelle»
Runnable
+sleep(in millis : boolean)
+interrupt()
+join()
+yield()
+isAlive() : boolean
+isDaemon() : boolean
+isInterrupted() : boolean
+setPriority(in newPriority : int)
+setDaemon(in on : boolean)
Thread1
-runnable
0..1
+enumerate(in list : Thread[]) : int
+enumerate(in list : ThreadGroup[]) : int
+interrupt()
+isDaemon()
+setDaemon()
+setDaemon()
+setPriority()
+activeCount() : int
+getParent() : ThreadGroup
+uncaughtException()
ThreadGroup
1
-threads
*
InterruptedException
Exception
+wait()
+wait(in timeout : long)
+notify()
+notifyAll()
Object
1
-subgroups *
SYSTEM SOFTWARE 5
Überblick über Klassen
Thread:
Thread-Objekte repräsentieren einen Thread
definiert Methoden für starten, unterbrechen, …
definiert static-Methoden, um
aktuell laufenden Thread zu steuern, zB ihn schlafen zu legen
Verwaltung aller aktuell existierenden Threads
Runnable:
Interface Runnable definiert Methode run(), welche den von einem Thread
auszuführenden Code enthält
Thread implementiert Runnable
Thread kann mit einem Runnable-Objekt erzeugt werden
Object:
in Klasse Object ist ein Monitor implementiert, d.h. jedes Objekt in Java kann
zur Thread-Synchronisation verwendet werden
wesentlichen Methoden sind wait und notify und notifyAll
ThreadGroup: Für das Bilden von Gruppen von Threads
InterruptedException: Exception geworfen bei Unterbrechung
SYSTEM SOFTWARE 6
Erzeugen, Starten, Ablauf eines Threads (1)
Variante: Ableiten von Thread
Thread wird abgeleitet (zB: BallThread) und
run() von Runnable überschrieben
Thread-Objekt wird mit new erzeugt
Thread wird mit start() gestartet und damit
run() ausgeführt
Der Thread läuft bis zum Ende der
Methode run() und stirbt dann
• Die static-Methode sleep(long millis) erlaubt
es, den aktuellen Thread für eine gegebene
Zeit „Schlafen zu legen“
Achtung:
sleep wirft InterruptedException und
muss daher mit try/catch-Anweisung
geklammert werden
class MyApp {
public static void main(...) {
Thread thread = new MyThread();
thread.start();
}
...
}
class MyThread extends Thread {
public void run() {
try {
...
Thread.sleep(1000);
}
catch (InterruptedException e){ ... }
}
}
Beispiel: BallThread
SYSTEM SOFTWARE 7
Erzeugen, Starten, Ablauf eines Threads
Die Methode run() wird in einem eigenen Runnable-Objekt implementiert
Thread-Objekt wird mit new erzeugt, wobei das Runnable-Objekt als Parameter
übergeben wird
Thread wird mit start() gestartet und damit run() des Runnable-Objekts ausgeführt
…
public void startApp…() {
Runnable runnable = new MyRunnable();
Thread thread = new Thread(runnable );
thread.start();
}
…
class MyRunnable implements Runnable {
public void run() {
try {
…
Thread.sleep(1000);
}
catch (InterruptedException e){ ... }
}
}
…
public void startApp…() {
Thread thread = new Thread(() -> {
try {
…
Thread.sleep(1000);
} catch (InterruptedException e) {
...
}
};
…
mit Lambdas von Java 8:
Lambda implementiert Runnable
SYSTEM SOFTWARE 8
Thread-Zustände
neu: wurde gerade erzeugt und noch
nicht gestartet
lauffähig: aktiv: wird gerade ausgeführt
bereit: kann ausgeführt werden und wartet
auf Zuteilung des Prozessors
blockiert: schlafend: wurde mit sleep schlafen gelegt
IO-blockiert: wartet auf Beendigung einer IO-
Operation
wartend: wurde mit wait in den wartenden
Zustand versetzt
gesperrt: Wartet auf die Aufhebung einer
Objekt-Sperre
suspendiert: durch suspend() vorübergehend
blockiert
Achtung: ist veraltet und sollte nicht
verwendet werden
tot: run()-Methode hat terminiert
blockiert (blocked)
lauffähig (runnable)
neu
(new)
aktiv
(active)
bereit
(ready)
tot
(dead)
schlafend
(sleeping)
IO-blockiert
(IO-blocked)
gesperrt
(locked)
wartend
(waiting)
sleep()
aufw
achen
IO-O
pe
rtio
n
Ende IO
-Opera
tion
Obje
kts
perr
e (
synchro
niz
ed)
Aufh
eben O
bje
kts
perre
wait-
Anw
eis
ung
notify / n
otifyA
ll
sta
rt()
run te
rmin
iert
suspendiert
(suspended)
susp
end() resum
e()
SYSTEM SOFTWARE 9
Scheduling und Prioritäten
Threads
geringer
Priorität
Threads
höhrerer
Priorität
Die lauffähigen Threads müssen sich den Prozessor zur Ausführung
teilen; sie konkurrieren um die Zuteilung des Prozessors
Java legt keine Zuteilungsstrategie fest; diese ist abhängig vom
Laufzeitsystem
Starvation
SYSTEM SOFTWARE 10
Scheduling und Prioritäten (2)
Mit der Methode
void setPriority(int priority)
kann man einem Thread eine Priorität für die Zuteilung geben
Prioritätswerte liegen zwischen MIN_PRIORITY = 0 undMAX_PRIORITY = 10 mit NORM_PRIORITY = 5 als Standardwert
Beispiel:
Mit der Methode
static void yield()
kann ein Thread seine Kontrolle des Prozessors abgeben und anderen die Chance zur Zuteilung geben.
Runnable r = new MyRunnable();
Thread thread = new Thread(r);
thread.setPriority(priority);
thread.start();
}
SYSTEM SOFTWARE 11
Unterbrechung und Terminieren
Unterbrechen von Threads durch
void interrupt()
d.h., befindet sich der Thread in einem blockierten Zustand, wird eine InterruptException
geworfen und der Thread aktiviert.
Mit static-Methode
static boolen interrupted()
wird für den aktuellen Thread der Interrupt-Status abgefragt und rückgesetzt (!)
Interrupts sind für die außerordentliche Terminierung eines Threads wichtig
Anmerkung: Dieses Vorgehen kann die gefährliche stop-Anweisung ersetzen, die nicht mehr
verwendet werden soll
public void run() {
while (!interrupted()) {
try {
// do something
sleep(1000);
} catch (InterruptedException e) { // Fange interrupt in sleep
interrupt(); // Rufe nochmals interrupt() auf,
// um interrupted() zu setzen
}
}
// terminieren
}
SYSTEM SOFTWARE 12
Multithreading
Einleitung und Grundlagen
Multithreading Grundlagen
Probleme bei Multithreading
Klassisches Modell
Synchronisation
Threads und Swing
Neues Modell
Executors und Futures
Synchronisation
Concurrent Collections
Fallbeispiel
Zusammenfassung
SYSTEM SOFTWARE 13
Probleme
Verzahnte Ausführung
Reihenfolge der Operationen
Unterbrechnung an beliebiger Stelle
Nicht-atomare Operationen
Sichtbarkeit von Daten zwischen Threads (stale data)
Data Race:Bei Schreib- und Leseoperationen in unterschiedlichen
Threads Reihenfolge nicht eindeutig!
SYSTEM SOFTWARE 14
Beispiel: Verzahnte Ausführung
Drei nebenläufige Threads:
• Main-Thread
• threadA
• threadB
Schreiboperationen in beliebiger
Reihenfolge
• a=1; x=b(0); b=1; y=a(1); (0, 1)
• b=1; y=a(0); a=1; x=b(1); (1, 0)
• a=1; b=1; y=a(1); x=b(1); (1, 1)
• ...
Umordnung von unabhängigen Statements
durch Compiler erlaubt
• x=b(0); y=a(0); a=1; b=1; (0, 0)
Sichtbarkeit nicht garantiert (stale data): Obwohl ein Thread Variable schreibt, muss neuer Wert
noch nicht für anderen Thread sichtbar sein!
• a=1; b=1; y=a(0); x=b(0); (0, 0)
public class Demo0_RaceConditions {
static int x = 0, y = 0;
static int a = 0, b = 0;
public static void main(String[] args)
throws InterruptedException {
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
a = 1;
System.out.println("a set");
x = b;
System.out.println("x set");
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
b = 1;
System.out.println("b set");
y = a;
System.out.println("y set");
}
});
threadA.start();
threadB.start();
ThreadA.join();
threadB.join();
System.out.println("(" + x + ", " + y + ")");
}
}
threadA ...
x = b;
a = 1;
threadB ...
y = a;
b = 1;
Obwohl threadA a = 1 gesetzt hat, liest threadB noch immer 0
SYSTEM SOFTWARE 15
Beispielproblem: Compileroptimierungen
Compiler optimiert Zugriffe für jeden Thread einzeln
Z.B. Register, Cache, Schleifen
class ValueStore implements Runnable {
public int value = 0;
@Override
public void run() {
while (value < 10) {
// no write in this thread
}
System.out.println(“value =" + value);
}
}
public static void main(String[] args) {
final ValueStore store = new ValueStore();
new Thread(store).start();
new Thread(new Runnable() {
@Override
public void run() {
for(;;) {
store.value++;
System.out.println(store.value);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
}
}
}
}).start();
}
Compiler kann while-Bedingung mit true ersetzen, weil value im Thread nicht verändert wird!
SYSTEM SOFTWARE 16
Beispielproblem: Keine atomaren Zugriffsoperationen
64-Bit Datentypen werden mit 2 Operationen geschrieben
Dazwischen kann Unterbrechung passieren
class ValueStore {
public long value = 0;
}
class Writer implements Runnable {
private ValueStore store;
Writer(ValueStore store) {
this.store = store;
}
@Override
public void run() {
for (;;) {
store.value = longOp();
try {
Thread.sleep(10);
} catch (InterruptedException e) {…
}
}
}
long longOp() { … }
}
class Reader implements Runnable {
private ValueStore store;
Reader(ValueStore store) {
this.store = store;
}
@Override
public void run() {
for (;;) {
System.out.println(store.value);
try {
Thread.sleep(10);
} catch (InterruptedException e) {…
}
}
}
}
Schreibzugriff nicht atomar
Lesezugriff kann ungültige Daten erhalten
SYSTEM SOFTWARE 17
Beispielproblem: Kein wechselseitiger Ausschluss
Beispiel Bank:
Methode transfer subtrahiert Betrag von einem Konto und addiert zum anderen
Mehrere Threads führen gleichzeitig Transfers aus
class Bank {
int[] accounts = new int[NACCOUNTS];
void transfer(int from, int to, int amount) {
account[from] = account[from] - amount;
account[to] = account[to] + amount;
}
thread1 acount[to]thread2
5000
Registerinhalt
laden
addieren
5500speichern
laden
addieren
speichern
5000
Registerinhalt
6000
5000
Inhalt
6000
5500
SYSTEM SOFTWARE 18
Beispielproblem: Ungültige Initialisierung
Problem: this-Zeiger verläßt Konstruktor bevor Konstruktor beendet
class EscapedThis {
public static List<EscapedThis> all = new ArrayList<EscapedThis>();
public String msg;
public EscapedThis(String msg) {
all.add(this); // this escapes here
…
this.msg = msg;
}
}
for (EscapedThis e : EscapedThis.all.toArray(new EscapedThis[0])) {
System.out.println(e.msg.length());
}
this wird in Liste all eingefügt, bevor Konstruktor beendet msg nicht gesetzt
Bei Iteration durch all, wird möglicherweiseauf unitialisiertes Objekt zugegriffen!
NullPointerException
SYSTEM SOFTWARE 19
Beispielproblem: Singleton-Pattern
Bei mehreren Threads ist Singleton nicht garantiert
public class SingletonNoLock {
private static SingletonNoLock instance;
public static SingletonNoLock getInstance() {
if (instance == null) {
// do something
instance = new SingletonNoLock();
n++;
}
return instance;
}
}
Dieser Block kann von mehrerenThreads betreten werden Singleton mehrmals erzeugt!
SYSTEM SOFTWARE 20
Beispielproblem: Stale Data
class MutableInt {
int x;
public void set(int x) {
this.x =x;
}
public int get() {
return x;
}
}
public class Demo6_MutableInt {
static MutableInt x = new MutableInt();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
int prev = -1;
for (;;) {
if (prev == x.get()) {
System.out.println("Something strange happened!! ");
}
prev = x.get();
x.set(prev + 1);
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
int prev = -1;
for (;;) {
if (prev == x.get()) {
System.out.println(“Something strange happened!! “);
}
prev = x.get();
x.set(prev + 1);
}
}
}).start();
}
}
Something strange happened!!
3814 3827
Something strange happened!!
4219 4231
Something strange happened!!
44871 44871
Something strange happened!!
48680 48680
Something strange happened!!
55090 55090
Sollte nicht passieren!
SYSTEM SOFTWARE 21
Beispielproblem: Unsichere Objektpublikation
public class Holder {
private int n;
public Holder(int n) {
this.n = n;
}
public void assertSanity() {
if (n != n)
throw new AssertionError(„This is due to unsafe publication!");
}
} Wie kann das passieren?
public class Dmeo7_UsafePublication {
static Holder h;
public static void main(String[] args) {
h = new Holder(3);
new Thread(new Runnable() {
public void run() {
h.assertSanity();
}
}
} Kann AssertionError werfen!
Grund: Stale Data
Sichtbarkeit von Objektreferenz
und Feldern zur gleichen Zeit
NICHT garantiert !
SYSTEM SOFTWARE 22
Maßnahmen
Zustandslose Methoden
Unveränderbare Objekte (d.h., nur final Felder)
sind grundsätzlich thread-safe
final Variablen vermeiden Stale Data
Single-Thread Rule: Objekte nur in einem Thread verwenden
z.B. Swing
typischerweise 1 Synchronisationspunkt
Thread-sichere Implementierungen mit
atomaren Zugriffen
wechselseitigem Ausschluss
sicherer Objektpublikation
Verwendung von Thread-sicheren Bausteinen
SYSTEM SOFTWARE 23
Thread-sichere Implementierungen
„Klasseninvarianten“ müssen auch bei nebenläufigen
Zugriffen immer erhalten bleiben
ohne dass der Client zusätzliche Maßnahmen treffen muss!
Definition: thread-safe
“A class is thread-safe if it behaves correctly when accessed from multiple threads,
regardless of the scheduling or interleaving of the execution of those threads by
the runtime environment, and with no additional synchronization or other
coordination on part of the calling code.”
From: B. Goetz, Java Concurrency in Practice, Addison-Wesley, 2006
SYSTEM SOFTWARE 24
Beispielproblem: Thread-sichere Klassen
public class Resource {
int n;
public Resource(int n) {
this.n = n;
}
public boolean seize() {
if (n > 0) {
n--;
return true;
} else {
return false;
}
}
public void release() {
n++;
}
}
Klasse ist nicht Thread-
sicher, weil Invariante
n >= 0 verletzt werden kann!
Durch Unterbrechung und seize() durchanderen Thread kann n hier 0 werden !!
Klasseninvariante: n >= 0
SYSTEM SOFTWARE 25
Java Memory Model (JMM):Reihenfolge von Operationen und Sichtbarkeit von Daten
Java Memory Model (JMM)
definiert Bedingungen über Reihenfolge von Operationen und Sichtbarkeit von Werten
bei Shared Data!
„happens-before Relation“
Partielle Ordnung für Synchronisationspunkte
Mit happens-before ist auch Sichtbarkeit der Daten verbunden!
thread A thread B
lock (M)
A
unlock(M)
lock (M)
B
unlock(M)
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
synchronized (M) {
A;
}
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
synchronized(M) {
B;
}
}
});
threadA.start();
threadB.start();
...happens-before
Schreiboperationen in A sichtbar in B
SYSTEM SOFTWARE 26
JMM: happens-before
JMM garantiert folgende happens-before Beziehungen
zwischen Synchronisationspunkten
Monitor: Unlock eines Monitors happens-before zeitlich folgende Lock des
selben Monitors
Volatile: Schreiboperation auf volatile Variable happens-before zeitlich
folgende Leseoperation
Thread start: Start eines Threads happens-before allen Operationen des
Threads
Thread termination: jede Operation im Thread happens-before ein anderer
Thread erfährt, dass der Thread terminiert ist
Interrupt: Aufruf von interrupt auf einen Thread happens-before der Thread
erkennt den Interrupt
Finalizer: Ende des Konstruktors happens-before Aufruf des Finalizers
Sequentielle Ordnung: Jeder Synchronisationspunkt happens-before jedem
zeitlich folgenden Synchronisationspunkt innerhalb des gleichen Threads
SYSTEM SOFTWARE 27
final Variablen im JMM
JMM behandelt final Variablen gesondert:
Bei final Variablen erfolgt ein value freece am Ende des Konstruktors
Damit ist auch Sichtbarkeit der final Werte für alle Threads gegeben!
Beispiel: Objektpublikation
public class Holder {
private final int n;
public Holder(int n) {
this.n = n;
}
public void assertSanity() {
if (n != n)
throw new AssertionError(„This is due to unsafe publication!");
}
}
Value freece für n Wert sichtbar in allen Threads
Bedingung kann NIE wahr sein!
SYSTEM SOFTWARE 28
Multithreading
Einleitung und Grundlagen
Multithreading Grundlagen
Probleme bei Multithreading
Klassisches Modell
Synchronisation
Threads und Swing
Neues Modell
Executors und Futures
Synchronisation
Concurrent Collections
Fallbeispiel
Zusammenfassung
SYSTEM SOFTWARE 29
volatile
Kennzeichnung von Variablen als volatile
Effekte von volatile:
Atomare Zugriff auch bei double und long
Sichtbarkeit von anderen Threads garantiert (keine stale Data)
class ValueStore implements Runnable {
public volatile int value = 0;
@Override
public void run() {
while (value < 10) {
// no write in this thread
}
System.out.println(“value =" + value);
}
}
public static void main(String[] args) {
final ValueStore store = new ValueStore();
new Thread(store).start();
new Thread(new Runnable() {
@Override
public void run() {
for(;;) {
store.value++;
System.out.println(store.value);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
}
}
}
}).start();
}
volatile bewirkt atomare Schreiboperationund aktuellen Wert für jeden Thread
SYSTEM SOFTWARE 30
Atomic Values
Wrapper-Klassen für Basisdatentypen und Referenzen
• mit atomarem Zugriff
• Zusammengesetzte Opertionen (test and act)
• Sichtbarkeit von mehreren Threads
public class Demo2_AtomicIntegerTest {
static AtomicInteger value = new AtomicInteger(10);
static class ValueWriter implements Runnable {
@Override
public void run() {
for (;;) {
if (! value.compareAndSet(0, 10)) {
System.out.println(value.getAndDecrement());
}
}
}
}
public static void main(String[] args) {
new Thread(new ValueWriter()).start();
new Thread(new ValueWriter()).start();
new Thread(new ValueWriter()).start();
new Thread(new ValueWriter()).start();
}
}
atomar: int prev = valuevalue = value - 1return prev
atomar: if (value == 0) {
value = 10; return true;
} else {return false;
}
SYSTEM SOFTWARE 31
Monitor
Basisklasse Object realisiert Monitor für wechselseitigen Ausschluss
Monitor hat einen Schlüssel (Lock) und verwaltet eine Queue
Threads können Lock anfordern
werden eventuell in der Queue als wartend auf den Lock gespeichert
bei Freiwerden des Locks erhält nächster in Queue den Lock und kann
Ausführung fortsetzen
damit kann ein beliebiger Code (aber insbesondere eine Methode des
Objekts) unter exklusivem Zugriff auf das Objekt ausgeführt werden
dies passiert, indem man eine Methode oder einen Block als
synchronized
deklariert
SYSTEM SOFTWARE 32
synchronized Methode
Wird eine Methode als synchronized deklariert, muss bei Ausführung der Methode der
Lock des Objekts (this) erhalten werden
Ist dieser nicht verfügbar, kann die Methode nicht begonnen und es muss auf die
Zuteilung des Locks gewartet werden
Bei statischen Methoden wird auf das Klassenobjekt synchronisiert
class Bank {
int[] accounts = new int[NACCOUNTS];
...
synchronized void transfer(int from, int to, int amount) {
accounts[from] -= amount;
accounts[to] += amount;
}
...
}
SYSTEM SOFTWARE 33
synchronized Block
Blöcke können auf ein beliebiges Objekt synchronized werden
Block kann nur betreten werden, wenn man den Lock des Objekts hat
class Bank {
private Object accountLock = new Object();
private Object customerLock = new Object();
...
void transfer(int from, int to, int amount) {
synchronized (accountLock) {
account[from] -= amount;
account[to] += amount;
}
}
void addCustomer(Customer customer) {
synchronized (customerLock) {
customers.add(customer);
}
}
}
SYSTEM SOFTWARE 34
wait und notify
Soll ein synchronisierter Code nicht fortgesetzt werden, kann er den Lock
zurückgeben und den Thread als „wartend auf das Objekt“ einreihen
Object stellt dazu Methoden zur Verfügung
wait() der Thread wird als wartend auf das Objekt blockiert;
Lock auf das Objekt wird freigegeben
wait(long timeout) wie wait, zusätzlich erfolgt nach timeout Millisekunden ein
Interrupt
notify() Es wird ein (!) auf das Objekt wartender Thread aufgeweckt
notifyAll() Es werden alle auf das Objekt wartenden Threads aufgeweckt
Beispiel: Warten bis Konto gefüllt
synchronized void transfer(int from, int to, int amount) throws InterruptedException {
while (accounts[from] < amount) {
wait();
}
accounts[from] -= amount;
accounts[to] += amount;
notifyAll();
}
SYSTEM SOFTWARE 35
Beispiel: BankAccounts (1)
Transferieren von Geld zwischen Konten in mehreren Threads
public class SynchBankTest {
public static final int NACCOUNTS = 10;
public static final int INITIAL_BALANCE = 10000;
public static void main(String[] args) {
Bank b = new Bank(NACCOUNTS, INITIAL_BALANCE);
int i;
for (i = 0; i < NACCOUNTS; i++) {
TransferThread t = new TransferThread(b, i, INITIAL_BALANCE);
t.setPriority(Thread.NORM_PRIORITY + i % 2);
t.start();
}
}
}
SYSTEM SOFTWARE 36
Beispiel: BankAccounts (2)
class Bank {
private final int[] accounts;
private long ntransacts;
public Bank(int n, int initialBalance) {
accounts = new int[n];
for (int i = 0; i < accounts.length; i++) {
accounts[i] = initialBalance;
}
ntransacts = 0;
}
public synchronized void transfer(int from, int to, int amount)
throws InterruptedException {
while (accounts[from] < amount) {
wait();
}
accounts[from] -= amount;
accounts[to] += amount;
ntransacts++;
notifyAll();
}
...
}
SYSTEM SOFTWARE 37
Beispiel: BankAccounts (3)
class TransferThread extends Thread {
private final Bank bank;
private final int fromAccount;
private final int maxAmount;
public TransferThread(Bank b, int from, int max) {
bank = b;
fromAccount = from;
maxAmount = max;
}
public void run() {
try {
while (!interrupted()) {
int toAccount = (int) (bank.size() * Math.random());
int amount = (int) (maxAmount * Math.random());
bank.transfer(fromAccount, toAccount, amount);
sleep(1);
}
} catch (InterruptedException e) {
}
}
}
SYSTEM SOFTWARE 38
Beispiel: Producer – Consumer (1)
public class ProducerConsumerApp {
public static void main(String[] args) {
Buffer buffer = new Buffer();
Producer p = new Producer(buffer);
Consumer c = new Consumer(buffer);
p.start(); // Starten Producer
c.start(); // Starten Consumer
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// nothing to do
} finally {
p.interrupt();
c.interrupt();
}
}
}
Producer produziert Elemente und schreibt sie in den Puffer
Consumer konsumiert produzierte Elemente aus dem Puffer
public class Buffer {
private Object obj = null;
synchronized public void put(Object o) {
obj = o;
}
synchronized public Object retrieve() {
Object o = obj;
obj = null;
return o;
}
synchronized public boolean isEmpty() {
return obj == null;
}
}
SYSTEM SOFTWARE 39
Beispiel: Producer – Consumer (2)
class Producer extends Thread {
private final Buffer buffer;
public Producer(Buffer buffer) { this.buffer = buffer; }
public void run() {
int i = 0;
while (!interrupted()) {
try {
synchronized (buffer) { // Sperren des Puffers
while (!buffer.isEmpty()) {
buffer.wait(); // warte auf buffer leer
}
Object o = new Integer(i++);
buffer.put(o); // Element produzieren
System.out.println(„Produzent erzeugte " + o);
buffer.notifyAll(); // benachrichtige wartende Consumer
}
Thread.sleep((int) (100 * Math.random())); // schlafen
} catch (InterruptedException e) {
interrupt();
}
}
}
}
SYSTEM SOFTWARE 40
Beispiel: Producer – Consumer (3)
class Consumer extends Thread {
private final Buffer buffer;
public Consumer(Buffer buffer) {
this.buffer = buffer;
}
public void run() {
while (!interrupted()) {
try {
synchronized (buffer) { // Sperren des Puffers
while (buffer.isEmpty()) {
buffer.wait(); // warte auf buffer nicht leer
}
Object o = buffer.retrieve(); // konsumieren
System.out.println("Konsument fand " + o);
buffer.notifyAll();
}
Thread.sleep((int) (100 * Math.random())); // schlafen
} catch (InterruptedException e) {
interrupt();
}
}
}
}
SYSTEM SOFTWARE 41
Collections: unsynchronisierte Collections
Collections sind NICHT thread-safe
Kann zu korrumpierten Daten führen
bei Änderung von Collection, über die man iteriert,
ConcurrentModificationException
static List<Integer> list = new ArrayList<Integer>();
public static void main(String[] args) {Thread a = new Thread(() -> {
for (int i = 1; i < 1000; i++) {…list.add(i);
}});a.start();
Thread b = new Thread(() -> {for (int i = 1000; i < 2000; i++) {
…list.add(i);
}});b.start();
for (int i : list) {…System.out.println(i);
}}
ConcurrentModificationExceptionbei Iteration
add nicht thread-safe
SYSTEM SOFTWARE 42
Synchronisierte Collections
Synchronisierte Wrapper für Collections haben thread-safe Zugriff
Achtung: Iteration nicht thread-safe!
static List<Integer> list = Collections.synchronizedList(new ArrayList<Integer>());
public static void main(String[] args) {Thread a = new Thread(() -> {
for (int i = 1; i < 1000; i++) {…list.add(i);
}});a.start();
Thread b = new Thread(() -> {for (int i = 1000; i < 2000; i++) {
…list.add(i);
}});b.start();
for (int i : list) {…System.out.println(i);
}}
add ist thread-safe!
synchronized Wrapper
Iteration NICHT thread-safe!
SYSTEM SOFTWARE 43
join
Oft ist es notwendig einen Thread zu erzeugen und den Ablauf mit diesem
abzustimmen
Die Anweisung join erlaubt es, auf einen Thread zu warten, bis dieser terminiert ist.
Beispiel:
Thread t = new Thread(...);
t.start();
// concurrent execution
t.join(); // wait for termination of Thread t
// continue
SYSTEM SOFTWARE 45
Ende der Applikation und Daemon-Threads
Eine Applikation wird beendet, wenn alle ihre Threads terminiert (tot) sind
Eine Ausnahme bilden dabei aber die sogenannten Daemon-Threads;
diese werden automatisch beendet, wenn der letzte Nicht-Daemon-Thread einer
Applikation terminiert hat
Daemon-Threads verwendet man daher für Hilfsdienste
Threads können durch Setzen der daemon-Property mit
void setDaemon(boolean on)
zu Daemon-Threads gemacht werden
SYSTEM SOFTWARE 46
Veraltete Methoden
stop:
Mit stop() kann man einen Thread „töten“; er wird sofort terminiert
sollte nicht verwendet werden, weil dadurch jede Aktion sofort beendet wird
und dadurch inkonsistente Zustände der bearbeiteten Objekte entstehen
können
Beispiel transfer() bei Bank: Es wird zwar von einem Konto noch
abgehoben aber auf das andere Konto nicht mehr gebucht
suspend / resume:
Mit suspend() kann ein Thread vorübergehend blockiert und mit resume()wieder aufgeweckt werden
dabei gibt er aber Locks von Objekten nicht frei (der Lock kann erst wieder
frei gegeben werden, wenn der Thread mit resume() wieder aufgeweckt
wird)
Dadurch können sehr leicht Deadlocks entstehen, die Verwendung von suspend
wird nicht empfohlen
SYSTEM SOFTWARE 47
Multithreading
Einleitung und Grundlagen
Multithreading Grundlagen
Probleme bei Multithreading
Klassisches Modell
Synchronisation
Threads und Swing
Neues Modell
Executors und Futures
Synchronisation
Concurrent Collections
Fallbeispiel
Zusammenfassung
SYSTEM SOFTWARE 48
Swing
Swing ist nicht thread-safe
Swing ist nach Single-Thread Regel gebaut
alle Aufrufe auf Swing-Komponenten in einem Thread
AWT-Thread
Arbeitet Ereignisqueue ab:
Zeichnen (paint)
Events: actionPerformed, mouseClicked, …
SYSTEM SOFTWARE 49
Blockieren von UI
Keine lang laufenden und blockierenden Aufrufe in Event-Methoden
blockieren UI
public class Demo1_BlockingCall {private static JFrame frame;private static JList list;private static DefaultListModel listModel;private static JButton addBtn;
public static void main(String[] args) throws InterruptedException {frame = new JFrame("AWT Thread Test");listModel = new DefaultListModel();list = new JList(listModel);frame.getContentPane().add(list, BorderLayout.CENTER);…addBtn = new JButton("Add");addBtn.addActionListener(new ActionListener() {
@Overridepublic void actionPerformed(ActionEvent e) {
Out.print("Wert eingeben: ");int x = In.readInt();listModel.addElement(x);
}});
…
Blockiert UIBlockiert UI
SYSTEM SOFTWARE 50
Eigener Thread
Ausführung lang laufender und blockierender Operationen in eigenem Thread
Updates außerhalb AWT Thread
Verletzt Single-Thread Regel von Swing!
…add1Btn.addActionListener(new ActionListener() {
@Overridepublic void actionPerformed(ActionEvent e) {
new Thread(new Runnable() {@Overridepublic void run() {
Out.print("Wert eingeben: ");x = In.readInt();listModel.addElement(x);
}}).start();
}});
in eigenem Thread ausgeführt
in AWT Thread ausgeführt
SYSTEM SOFTWARE 51
Eigener Thread
Führt zu Fehlverhalten von Swing
private static int y = 0;public static void main(String[] args) throws InterruptedException {
…add100Btn = new JButton("Add 100");add100Btn.addActionListener(new ActionListener() {
@Overridepublic void actionPerformed(ActionEvent e) {
new Thread(new Runnable() {
@Overridepublic void run() {
for (int i = 0; i < 100; i++) {listModel.addElement(y++);
}}
}).start();}
});
JList funktioniert nicht mehr!
Anfügen von 100 Elementen in ListModel
SYSTEM SOFTWARE 52
invokeLater und invokeAndWait
Über EventQueue lassen sich Tasks in die AWT Ereignisqueue einfügen
invokeLater: Einfügen als zukünftige Aufrufe (asynchron)
invokeAndWait: Einfügen und blockieren bis ausgeführt
Merke: Alle Operationen aus anderen Threads, die UI-Elemente updaten, sollen über invokeLater oder invokeAndWait abgesetzt werden!
private static int y = 0;public static void main(String[] args) throws InterruptedException {
…add1Btn.addActionListener(new ActionListener() {
@Overridepublic void actionPerformed(ActionEvent e) {
new Thread(new Runnable() {@Overridepublic void run() {
Out.print("Wert eingeben: ");x = In.readInt();EventQueue.invokeLater(new Runnable() {
public void run() {listModel.addElement(x);
}});
}}).start();
}});
Einplanen des Updates in AWT EventQueue
SYSTEM SOFTWARE 53
invokeLater und invokeAndWait
Beispiel invokeAndWait: Anfügen von 100 Elementen
private static int y = 0;public static void main(String[] args) throws InterruptedException {
…add100Btn.addActionListener(new ActionListener() {
@Overridepublic void actionPerformed(ActionEvent e) {
new Thread(new Runnable() {@Overridepublic void run() {
for (int i = 0; i < 100; i++) {y++;try {
EventQueue.invokeAndWait(new Runnable() {public void run() {
listModel.addElement(y);}
});} catch (InterruptedException e) {} catch (InvocationTargetException e) {}
}}
}).start();}
});
Blockiert bis AWT Thread Task ausgeführt hat
SYSTEM SOFTWARE 54
invokeLater und invokeAndWait
Beispiel invokeLater:
Mit invokeLater werden Tasks nur abgesetzt
AWT Thread führt Tasks später aus
Absetzen und Ausführung zu unterschiedlichen Zeiten
add100LaterBtn.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {
new Thread(new Runnable() {@Overridepublic void run() {
for (int i = 0; i < 100; i++) {y++;EventQueue.invokeLater(new Runnable() {
public void run() {listModel.addElement(y);
}});try { Thread.sleep(1); } catch (InterruptedException e) {}
}}
}).start();}
});
Einplanen von Task
Bei Ausführung durch AWT Thread ist Wert von y schon 100
SYSTEM SOFTWARE 55
Multithreading
Einleitung und Grundlagen
Multithreading Grundlagen
Probleme bei Multithreading
Klassisches Modell
Synchronisation
Threads und Swing
Neues Modell
Executors und Futures
Synchronisation
Concurrent Collections
Fallbeispiel
Zusammenfassung
SYSTEM SOFTWARE 56
Executors und Futures
Executors, ExecutorService
Ausführung von Task in mehrere Threads
Abstraktion von konkreten Threads
Future<T>
Ergebnis und Steuerung von asynchronen Berechnungen
Callable<T>
Ausführbares Objekt analog zu Runnable
mit Rückgabewert vom Typ T
seit Java 1.5
Bei vielen Tasks wesentlich effizienter als Threads!
SYSTEM SOFTWARE 57
Callable, Future, FutureTask
Callable<T>-Intefacewie Runnable aber erlaubt Rückgabewerte (vom Typ T)
Future<T>-Interfacerepräsentiert Ergebnis einer asynchronen Berechnung
erlauben Zugriff und Eingriff in Ausführung
RunnableFuture<T>-InterfaceKombination von Runnable und Future
FutureTask<T>-KlasseImplementierung von RunnableFuture
public class FutureTask<V> extends Object implements RunnableFuture<V> {
…
}
public interface Future<V> {
public boolean isCancelled()
public boolean isDone()
public boolean cancel(boolean mayInterruptIfRunning)
public V get() throws InterruptedException, ExecutionException
public V get(long timeout, TimeUnit unit)
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run()
}
public interface Callable<V> {
V call() throws Exception
}
get-Methoden blockieren, bis Berechnung fertig
SYSTEM SOFTWARE 58
Executor / ExecutorService
erlaubt Berechnungen abzusetzen
in ThreadPool auszuführen
Ergebnis über Future abzuholen
Steuerung der Ausführungseinheit (ThreadPool)
public interface Executor {
void execute(Runnable command)
}
public interface ExecutorService extends Executor {
void shutdown()
List<Runnable> shutdownNow()
boolean isShutdown()
boolean isTerminated()
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException
<T> Future<T> submit(Callable<T> task)
<T> Future<T> submit(Runnable task, T result)
Future<?> submit(Runnable task)
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException
}
SYSTEM SOFTWARE 59
ThreadPool
Implementierung von ExecutorService
mittels einer Menge von Threads
Threads für unterschiedliche Berechnungen wiederverwendbar
durch statische Methoden von Executors erzeugt
Klasse ThreadPoolExecutor erlaubt Konfiguration des ThreadPools
Threads sind eine teure Ressource!
public class Executors {
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newCachedThreadPool()
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
public static ExecutorService newSingleThreadExecutor()
...
}
public class ThreadPoolExecutor extends AbstractExecutorService {
public int getActiveCount()
public long getTaskCount()
public long getCompletedTaskCount()
public int getPoolSize()
public int getCorePoolSize()
public void setCorePoolSize(int corePoolSize)
public int getMaximumPoolSize()
public void setMaximumPoolSize(int maximumPoolSize)
...
}
SYSTEM SOFTWARE 60
Executor / ExecutorService
Beispiel
public class ExecutorServiceDemo {
public static void main(String[] args)
throws InterruptedException, ExecutionException {
ExecutorService e = Executors.newFixedThreadPool(10);
Future<Integer> future = e.submit(() -> {
…
// long lasting computation
…
return result;
});
// continue with some with some other work in main thread
int result = future.get();
}
}
Erzeugen eines ExecutorService überstatische Methoden von Executors
Anfügen von Runnables und Callablesbeim ExecutorService
Abholen der Ergebnisse der Callables später über Future
SYSTEM SOFTWARE 64
Fork-Join Pool
Fork-Join API
ForkJoinPool stellt Thread-Pool mit „Work-Stealing“ bereit
Tasks werden im ForkJoinPool ausgeführt und können weitere Tasks erzeugen
Im ForkJoinPool erfoglt „Work Stealing“, d.h. sobald ein Task blockiert, wird
Thread freigegeben, damit er andere Aufgaben übernehmen kann
RecursiveTask und RecursiveAction sind Basisklassen für rekursive hierarchische
Taskstrukturen
ForkJoinTask<V>
ForkJoinTask<V> fork()
static void invokeAll
(ForkJoinTask<?>... tasks)
V join()
...
ForkJoinPool
invoke(ForkJoinTask)
execute(ForkJoinTask)
submit(ForkJoinTask)
RecursiveTask<V>
abstract V compute
RecursiveAction
abstract void compute
SYSTEM SOFTWARE 65
Ausführung durch Fork-Join
Fork-Join funktioniert nach dem Divide&Conquer Prinzip
RecursiveTasks spalten rekursiv Untertasks ab
diese werden durch Fork-Join Thread-Pool ausgeführt
Prinzip von RecursiveTasks
public class MyRecursiveTask<T> extends RecursiveTask<T> {
@Overrideprotected T compute() {
if (problem small) {result = solve problem sequentially return result;
}
split sub-problem and create subtask for sub-problem send task to fork-join pool... possibley more splits
join with subtasks for partial solutions
result = combine partial solutions return result;
}
Ist Problem klein genug,
löse es sequentiell
Spalte Teilprobleme ab und schicke diese zur
Ausführung an Fork-Join Thread-Pool
Warte auf Lösung der Teilprobleme
Kombiniere Teilprobleme und gib
Gesamtlösung zurück
SYSTEM SOFTWARE 66
Fork-Join Pool: Work Stealing
Fork-Join Pool
fixe Anzahl von Threads (abhängig von Anzahl Cores, z.B. 3)
jeder Thread verwaltet eine Queue von Tasks
fork() spaltet Task ab und stellt in Queue des aktuellen Threads
arbeitet mit Work Stealing
untätige Threads „stehlen“ sich Tasks aus Queue anderer Threads
Threads die Tasks ausführen, die mit join() auf Ergebnisse anderer Tasks warten,
suchen sich anderen Aufgaben
T
2
worker-1
T1
worker-2
T
3
fork()
fork()T
3steal T3
Damit wird eine gleichmäßige Auslastung der Threads erreicht!
SYSTEM SOFTWARE 68
ScheduledExecutorService
verzögerte Aktivitäten
periodische Aktivitäten
CompletionService<V>, ExecutorCompletionService<V>
unterstützt asynchrones Abholen von Ergebnissen der gesendeten Aufgaben
Weitere Klassen
public interface ScheduledExecutorService extends ExecutorService {
<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)
ScheduledFuture<?> schedule(Runnable cmd, long delay, TimeUnit unit)
ScheduledFuture<?> scheduleAtFixedRate(Runnable cmd, long initDelay, long period, TimeUnit unit)
...
}
public interface CompletionService<V> {
Future<V> submit(Callable<V> task)
Future<V> submit(Runnable task, V result)
Future<V> take() throws InterruptedException
Future<V> poll()
Future<V> poll(long timeout, TimeUnit unit)
throws InterruptedException // poll with waiting time
}
public class ExecutorCompletionService<V> implements CompletionService<V> {
public ExecutorCompletionService(Executor executor)
...
}
Blockiert, bis nächstes Ergebnis verfügbar
liefert nächstes Ergebnis oder null, wenn keines verfügbar
wie poll aber wartet timeoutZeiteinheiten
SYSTEM SOFTWARE 69
Multithreading
Einleitung und Grundlagen
Multithreading Grundlagen
Probleme bei Multithreading
Altes Modell
Synchronisation
Threads und Swing
Neues Modell
Executors und Futures
Synchronisation
Concurrent Collections
Fallbeispiel
Zusammenfassung
SYSTEM SOFTWARE 70
Synchronizers
Lock und Conditions
effizientere und flexiblere Variante von Monitor bei Object
CountDownLatch
Initialisierung mit fixem Wert
await(): Warten bis Zähler auf 0
countDown(): Herunterzählen
Semaphore
Zugriff auf beschränkte Resource mit n Berechtigungen
über acquire() und release()
CyclicBarrier
Zusammenkunft mehrerer Threads
Exchanger
Datenaustausch
SynchronousQueue
Synchroner Übergabe eines Wertes
SYSTEM SOFTWARE 71
Lock und Condition
Lock und Condition als effizientere und flexiblere Alternative zu Object
class BankWithLock {
private Lock bankLock;
private Condition sufficientFunds;
…
public BankWithLock(int n, double initialBalance) {
bankLock = new ReentrantLock();
sufficientFunds = bankLock.newCondition();
accounts = new double[n];
for (int i = 0; i < accounts.length; i++)
accounts[i] = initialBalance;
}
public void transfer(int from, int to, double amount) throws InterruptedException {
bankLock.lock();
try {
while (accounts[from] < amount)
sufficientFunds.await();
}
accounts[from] -= …
sufficientFunds.signalAll();
} finally {
bankLock.unlock();
}
}
…
SYSTEM SOFTWARE 72
Lock und Condition
Locks für jedes Konto
class BankWithMultipleLocks {
private Lock[] accountLocks;
private Condition[] sufficientFunds;
public BankWithLock(int n, double initialBalance) {
accounts = new double[n];
accountLocks = new ReentrantLock[n];
sufficientFunds = new Condition[n];
for (int i = 0; i < accounts.length; i++) {
accountLocks[i] = new ReentrantLock();
sufficientFunds[i] = accountLocks[i].newCondition();
accounts[i] = initialBalance;
}
}
public void transfer(int thread, int from, int to, double amount)throws … {
accountLocks[from].lock();
accountLocks[to].lock();
try {
while (accounts[from] < amount)
sufficientFunds[from].await();
accounts[from] -= amount;
accounts[to] += amount;
sufficientFunds[to].signalAll();
} finally {
accountLocks[from].unlock();
accountLocks[to].unlock();
}
}
}
Deadlock!
SYSTEM SOFTWARE 74
Lock: tryLock
tryLock mit Timeout verhindert Deadlocks
…
public void transfer(int thread, int from, int to, double amount) throws … {
System.out.println(thread + ": waiting " + from);
if (accountLocks[from].tryLock(1000, TimeUnit.MILLISECONDS)) {
try {
if (accountLocks[to].tryLock(1000, TimeUnit.MILLISECONDS)) {
try {
while (accounts[from] < amount) sufficientFunds[from].await();
accounts[from] -= amount;
accounts[to] += amount;
sufficientFunds[to].signalAll();
} finally {
accountLocks[to].unlock();
}
} else {
System.out.println(thread + ":failed to get lock for " + to);
}
} finally {
accountLocks[from].unlock();
}
} else {
System.out.println(thread + ":failed to get lock for " + from);
}
}
…
SYSTEM SOFTWARE 75
Beispiel CountDownLatch
public class CountDownLatchDemo {private CountDownLatch startGate; private CountDownLatch endGate;
public void startTasks(Task[] tasks) {try {
startGate = new CountDownLatch(1); endGate = new CountDownLatch(tasks.length); for (Task task : tasks) {
new Thread(task).start(); }System.out.println("startet"); long startTime = System.nanoTime(); startGate.countDown(); endGate.await(); System.out.format("finished with run time %d", System.nanoTime() - startTime);
} catch (InterruptedException ex) { … }}
class Task implements Runnable {@Override
public void run() {try {
startGate.await(); // do something …..
} finally {endGate.countDown();
}}
}…
}
wait that all tasks are started
wait that all tasks are finished
SYSTEM SOFTWARE 76
Multithreading
Einleitung und Grundlagen
Multithreading Grundlagen
Probleme bei Multithreading
Klassisches Modell
Synchronisation
Threads und Swing
Neues Modell
Executors und Futures
Synchronisation
Concurrent Collections
Fallbeispiel
Zusammenfassung
SYSTEM SOFTWARE 77
Concurrent Collections
Package java.util.concurrent
Optimiert für Zugriffe aus mehreren Threads
Werfen keine ConcurrentModificationExceptions
Erweiterte atomare Zugriffe wie z.B. putIfAbsent, replace
Interface Concurrent Implemenation
Map ConcurrentHashMap
Set CopyOnWriteArraySet
SortedMap ConcurrentSkipListMap
SortedSet ConcurrentSkipListSet
List CopyOnWriteArrayList
BlockingQueue ArrayBlockingQueue
ConcurrentLinkedQueue
The Iterator is a "weakly consistent" iterator that will never throw ConcurrentModificationException,
and guarantees to traverse elements as they existed upon construction of the iterator,
and may (but is not guaranteed to) reflect any modifications subsequent to construction.
SYSTEM SOFTWARE 78
Concurrent Collections
Synchronisierte Wrapper für Collections haben thread-safe Zugriff
Auch Iteration nun thread-safe!!
static Queue<Integer> list = new ConcurrentLinkedQueue<Integer>();
public static void main(String[] args) {Thread a = new Thread(() -> {
for (int i = 1; i < 1000; i++) {…list.add(i);
}});
a.start();
Thread b = new Thread(() -> {for (int i = 1000; i < 2000; i++) {
…list.add(i);
}});b.start();
for (int i : list) {…System.out.println(i);
}
add ist thread-safe!
concurrent collection
Iteration thread-safe!
SYSTEM SOFTWARE 79
BlockingQueue
Methoden mit unterschiedlichen Reaktionen, wenn Bedingung für
Zugriff nicht erflüllt (Liste leer oder voll):
add, remove, element werfen Exceptions
put und take blockieren bis Bedingung erfüllt
offer, poll und peek liefern Werte, die Erfolg oder Misserfolg anzeigen
offer und poll blockieren bestimmte Zeit, bis Bedingung erfüllt; kehren dann
mit Rückgabewert zurück
Throw
exceptions
Return special
valueBlock Time out
Insert add(e) offer(e) put(e) offer(e, timeout, timeunit)
Remove remove() poll() take() poll(timeout, timeunit)
Examine element() peek() not applicable not applicable
Blockieren für angegebeneTimeout-Zeit!
SYSTEM SOFTWARE 80
Beispiel: Producer-Consumer mit BlockingQueue
public class ProducerConsumerApp {private static final int CAPACITY = 3;public static void main(String[] args) {
BlockingQueue<Integer> buffer = new ArrayBlockingQueue<Integer>
(CAPACITY, true);Producer p1 = new Producer(buffer);Producer p2 = new Producer(buffer);Consumer c1 = new Consumer(buffer);Consumer c2 = new Consumer(buffer);p1.start(); p2.start(); c1.start(); c2.start(); …
}}
import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.BlockingQueue;class Producer extends Thread {
private BlockingQueue<Integer> buffer;public Producer(BlockingQueue buffer) {
this.buffer = buffer;}public void run() {
int i = 0;while (!interrupted()) {
try {Integer o = new Integer(i++);buffer.put(o); System.out.println("Produziert " + o);
} catch (InterruptedException ex) {}
}}
}class Consumer extends Thread {
private BlockingQueue buffer;public Consumer(BlockingQueue buffer) {
this.buffer = buffer;}public void run() {
while (!interrupted()) {try {
Object o = buffer.take(); System.out.println("Konsumiert " + o);
} catch (InterruptedException ex) {}
}}
}
Blockiert, wenn buffer voll
Blockiert, wenn buffer leer
This class supports an optional fairness policy for ordering
waiting producer and consumer threads. By default, this
ordering is not guaranteed. However, a queue constructed
with fairness set to true grants threads access in FIFO
order. Fairness generally decreases throughput but reduces
variability and avoids starvation.
SYSTEM SOFTWARE 81
Multithreading
Einleitung und Grundlagen
Multithreading Grundlagen
Probleme bei Multithreading
Klassisches Modell
Synchronisation
Threads und Swing
Neues Modell
Executors und Futures
Synchronisation
Concurrent Collections
Fallbeispiel
Zusammenfassung
SYSTEM SOFTWARE 82
Beispiel Total File Size
Folgend mehrere Varianten eines Verfahrens zur Ermittlung der
Gesamtgröße der Files in Directory
Sequentielles Verfahren: public class SequentialTotalFileSize {
private long getTotalSizeOfFilesInDir(final File file) {if (file.isFile())
return file.length();final File[] children = file.listFiles();long total = 0;if (children != null)
for (final File child : children)total += getTotalSizeOfFilesInDir(child);
return total;}
public static void main(final String[] args) {final long start = System.nanoTime();final long total = new TotalFileSizeSequential().getTotalSizeOfFilesInDir(new File(args[0]));final long end = System.nanoTime();System.out.println("TotalFileSizeSequential");System.out.println("Total Size: " + total);System.out.println("Time taken: " + (end - start) / 1.0e9);
}}
> java SequentialTotalFileSize C:\Users\hpSequentialTotalFileSizeTotal Size: 83623093266Time taken: 34.442712065
> java SequentialTotalFileSize C:\Users\hpSequentialTotalFileSizeTotal Size: 83689700425Time taken: 17.180551995
Ergebnisse sehr abhängig von Caching im Betriebssystem !
aus: V. Subramaniam: Programming Concurrency on the JVM
SYSTEM SOFTWARE 83
Beispiel Total File Size: Variante 1 (1/2)
Total File Size mit Callables und Futures public class NaivelyConcurrentTotalFileSize {
private long getTotalSizeOfFilesInDir(final ExecutorService service, final File file) throws InterruptedException, ExecutionException, TimeoutException {
if (file.isFile())return file.length();
long total = 0;final File[] children = file.listFiles();if (children != null) {
final List<Future<Long>> partialTotalFutures = new ArrayList<Future<Long>>();for (final File child : children) {
partialTotalFutures.add(service.submit(new Callable<Long>() {public Long call() throws InterruptedException, ExecutionException, TimeoutException {
return getTotalSizeOfFilesInDir(service, child);}
}));}for (final Future<Long> partialTotalFuture : partialTotalFutures)
total += partialTotalFuture.get(100, TimeUnit.SECONDS);}return total;
}
private long getTotalSizeOfFile(String fileName) throws InterruptedException, ExecutionException, TimeoutException {
final ExecutorService service = Executors.newFixedThreadPool(100);try {
return getTotalSizeOfFilesInDir(service, new File(fileName));} finally {
service.shutdown();}
}...
get ist ein blockierenden Aufruf Task ist blockiert, belegt aber weiterhin den Thread
aus: V. Subramaniam: Programming Concurrency on the JVM
SYSTEM SOFTWARE 84
Beispiel Total File Size: Variante 1 (2/2)
Analyse:
Nach Absetzen der Callables ist Task in Future.get blockiert
Thread wird aber nicht frei gegeben
Dem System gehen die Threads in ThreadPool aus
Resultat ist ein „pool induced deadlock“
Arbeiten mit Futures allgemein problematisch, weil Ergebnis nur durch
blockierenden Call abholbar!
...public static void main(final String[] args) throws InterruptedException,
ExecutionException, TimeoutException {final long start = System.nanoTime();final long total = new NaivelyConcurrentTotalFileSize().getTotalSizeOfFile(args[0]);final long end = System.nanoTime();System.out.println("NaivelyConcurrentTotalFileSize");System.out.println("Total Size: " + total);System.out.println("Time taken: " + (end - start) / 1.0e9);
}}
> java NaivelyConcurrentTotalFileSize C:\Users\hpException in thread "main" java.util.concurrent.TimeoutExceptionat java.util.concurrent.FutureTask.get(FutureTask.java:201)at pcj.NaivelyConcurrentTotalFileSize.getTotalSizeOfFilesInDir(NaivelyConcurrentTotalFileSize.java:44)at pcj.NaivelyConcurrentTotalFileSize.main(NaivelyConcurrentTotalFileSize.java:64)
Verfahren bricht mit Timeout ab!
aus: V. Subramaniam: Programming Concurrency on the JVM
SYSTEM SOFTWARE 85
Beispiel Total File Size: Variante 2 (1/2)
Total File Size mit Sammlung von Teilergebnissen eines Directories ohne
rekursive Aufrufepublic class ConcurrentTotalFileSize {
private long getTotalSizeOfFilesInDir(final File file)throws InterruptedException, ExecutionException, TimeoutException {
final ExecutorService service = Executors.newFixedThreadPool(100);
try {long total = 0;final List<File> directories = new ArrayList<File>();directories.add(file);while (!directories.isEmpty()) {
final List<Future<SubDirsAndSize>> partialResults = new ArrayList<Future<SubDirsAndSize>>();for (final File directory : directories) {
partialResults.add(service.submit(new Callable<SubDirsAndSize>() {public SubDirsAndSize call() {
return getTotalAndSubDirs(directory);}
}));}directories.clear();for (final Future<SubDirsAndSize> partialResultFuture : partialResults) {
final SubDirsAndSize subDirectoriesAndSize = partialResultFuture.get(100, TimeUnit.SECONDS);directories.addAll(subDirectoriesAndSize.subDirs);total += subDirectoriesAndSize.size;
}}return total;
} finally {service.shutdown();
}}...
class SubDirsAndSize {final public long size;final public List<File> subDirs;
public SubDirsAndSize(long ts, List<File> sd) {size = ts;subDirs = sd;
}}
Absetzen von Task pro File
Abholen der Ergebnisse
Aufnahme der gefundenen Subdirectories in Liste der noch zu verarbeitenden
Solange nicht alle Subdirectories
verarbeitet
aus: V. Subramaniam: Programming Concurrency on the JVM
SYSTEM SOFTWARE 86
Beispiel Total File Size: Variante 2 (2/2)
...private SubDirectoriesAndSize getTotalAndSubDirs(final File file) {
long total = 0;final List<File> subDirectories = new ArrayList<File>();if (file.isDirectory()) {
final File[] children = file.listFiles();if (children != null)
for (final File child : children) {if (child.isFile())
total += child.length();else
subDirectories.add(child);}
}return new SubDirectoriesAndSize(total, subDirectories);
}
public static void main(final String[] args) throws InterruptedException,ExecutionException, TimeoutException {
final long start = System.nanoTime();final long total = new ConcurrentTotalFileSize().getTotalSizeOfFilesInDir(new File(args[0]));final long end = System.nanoTime();System.out.println("ConcurrentTotalFileSize");System.out.println("Total Size: " + total);System.out.println("Time taken: " + (end - start) / 1.0e9);
}}
> java ConcurrentTotalFileSize C:\Users\hpConcurrentTotalFileSizeTotal Size: 5696929234Time taken: 13.404754691
> java ConcurrentTotalFileSize C:\Users\hpConcurrentTotalFileSizeTotal Size: 5695150268Time taken: 11.58708715
aus: V. Subramaniam: Programming Concurrency on the JVM
SYSTEM SOFTWARE 87
Beispiel Total File Size: Variante 3 (1/2)
Total File Size mit Teilergebnisse von Tasks in AtomicLong addiert
und CountDownLatch zur Synchronisation
public class ConcurrentTotalFileSizeWLatch {
private ExecutorService service;final private AtomicLong pendingTasks = new AtomicLong();final private AtomicLong totalSize = new AtomicLong();final private CountDownLatch latch = new CountDownLatch(1);
private void updateTotalSizeOfFilesInDir(final File file) {long fileSize = 0;if (file.isFile())
fileSize = file.length();else {
final File[] children = file.listFiles();if (children != null) {
for (final File child : children) {if (child.isFile())
fileSize += child.length();else {
pendingTasks.incrementAndGet();service.execute(new Runnable() {
public void run() {updateTotalSizeOfFilesInDir(child);
}});
}}
}}totalSize.addAndGet(fileSize);if (pendingTasks.decrementAndGet() == 0)
latch.countDown();}...
Absetzen von Task pro FileRaufzählen von pendingTasks
Update von fileSize thread-safe
Update pendingTasks + CountDownLatch thread-safe
aus: V. Subramaniam: Programming Concurrency on the JVM
thread-safe Objekte
SYSTEM SOFTWARE 88
Beispiel Total File Size: Variante 3 (2/2)
ConcurrentTotalFileSizeWLatch
Total Size: 83663127072
Time taken: 20.903119452
...private long getTotalSizeOfFile(final String fileName)
throws InterruptedException {service = Executors.newFixedThreadPool(100);pendingFileVisits.incrementAndGet();try {
updateTotalSizeOfFilesInDir(new File(fileName));latch.await(100, TimeUnit.SECONDS);return totalSize.longValue();
} finally {service.shutdown();
}}
public static void main(final String[] args) throws InterruptedException,ExecutionException, TimeoutException {
final long start = System.nanoTime();final long total = new ConcurrentTotalFileSizeWLatch().getTotalSizeOfFile(args[0]); final long end = System.nanoTime();System.out.println("ConcurrentTotalFileSizeWLatch");System.out.println("Total Size: " + total);System.out.println("Time taken: " + (end - start) / 1.0e9);
}}
> java ConcurrentTotalFileSizeWLatch C:\Users\hpConcurrentTotalFileSizeWLatchTotal Size: 83625673020Time taken: 17.499908567
Start mit Root-Directory
Warten bis alle Tasks fertig!
> java ConcurrentTotalFileSizeWLatch C:\Users\hpConcurrentTotalFileSizeWLatchTotal Size: 83663127072Time taken: 12.961295139
aus: V. Subramaniam: Programming Concurrency on the JVM
SYSTEM SOFTWARE 89
Beispiel Total File Size: Variante 4 (1/2)
TotalFile Size mit Queue mit Teilergebnissenpublic class ConcurrentTotalFileSizeWQueue {
private ExecutorService service;final private BlockingQueue<Long> fileSizes = new ArrayBlockingQueue<Long>(500);final AtomicLong pendingTasks = new AtomicLong();
private void startExploreDir(final File file) {pendingTasks.incrementAndGet();service.execute(new Runnable() {
public void run() {exploreDir(file);
}});
}private void exploreDir(final File file) {
long fileSize = 0;if (file.isFile())
fileSize = file.length();else {
final File[] children = file.listFiles();if (children != null)
for (final File child : children) {if (child.isFile())
fileSize += child.length();else
startExploreDir(child);}
}try {
fileSizes.put(fileSize);} catch (Exception ex) {
throw new RuntimeException(ex);}pendingTasks.decrementAndGet();
}...
Absetzen von Task pro FileRaufzählen von pendingTasks
Anfügen des Teilerbenisses in queue thread-safe
thread-safe Objekte
aus: V. Subramaniam: Programming Concurrency on the JVM
SYSTEM SOFTWARE 90
Beispiel Total File Size: Variante 4 (2/2)
...private long getTotalSizeOfFile(final String fileName)
throws InterruptedException {service = Executors.newFixedThreadPool(100);try {
startExploreDir(new File(fileName));long totalSize = 0;while (pendingTasks.get() > 0 || fileSizes.size() > 0) {
final Long size = fileSizes.poll(10, TimeUnit.SECONDS);totalSize += size;
}return totalSize;
} finally {service.shutdown();
}}
public static void main(final String[] args) throws InterruptedException {final long start = System.nanoTime();final long total = new ConcurrentTotalFileSizeWQueue().getTotalSizeOfFile(args[0]);final long end = System.nanoTime();System.out.println("ConcurrentTotalFileSizeWQueue");System.out.println("Total Size: " + total);System.out.println("Time taken: " + (end - start) / 1.0e9);
}}
> java ConcurrentTotalFileSizeWQueue C:\Users\hpConcurrentTotalFileSizeWQueue Total Size: 83661507904Time taken: 13.3983663
Verarbeitung der Ergebnisse in der Queue!
> java ConcurrentTotalFileSizeWQueue C:\Users\hpConcurrentTotalFileSizeWQueueTotal Size: 83691338377Time taken: 10.754997324
aus: V. Subramaniam: Programming Concurrency on the JVM
SYSTEM SOFTWARE 91
Beispiel Total File Size: Variante 5 (1/2)
TotalFile Size mit Fork-Join API
public class ForkJoinTotalFileSize {private final static ForkJoinPool forkJoinPool = new ForkJoinPool();
private static class FileSizeFinder extends RecursiveTask<Long> {final File file;public FileSizeFinder(final File theFile) {
file = theFile;}
@Overridepublic Long compute() {
long size = 0;if (file.isFile()) {
size = file.length();} else {
final File[] children = file.listFiles();if (children != null) {
List<ForkJoinTask<Long>> tasks = new ArrayList<ForkJoinTask<Long>>();for (final File child : children) {
if (child.isFile()) {size += child.length();
} else {tasks.add(new FileSizeFinder(child));
}}for (final ForkJoinTask<Long> task : invokeAll(tasks)) {
size += task.join();}
}}return size;
}}...
Absetzen von Subtask pro File:rekursiv !!
blockierendes Warten und Abholen des Ergebnisses
Task durch Ableiten von RecursiveTask<T>
Überschreiben von compute von RecursiveTask<T>
Erzeugen der Subtasks
Thread-Pool zur Ausführung der Tasks
aus: V. Subramaniam: Programming Concurrency on the JVM
SYSTEM SOFTWARE 92
Beispiel Total File Size: Variante 5 (2/2)
...public static void main(final String[] args) {
final long start = System.nanoTime();final long total = forkJoinPool.invoke(new FileSizeFinder(new File(args[0])));final long end = System.nanoTime();System.out.println("ForkJoinTotalFileSize");System.out.println("Total Size: " + total);System.out.println("Time taken: " + (end - start) / 1.0e9);
}}
> java ForkJoinTotalFileSize C:\Users\hpForkJoinTotalFileSizeTotal Size: 83626897049Time taken: 13.98460454
Verfahren ähnlich wie Variante 1: rekursive Taskstruktur analog zur
rekursiven Directory-Struktur
Tasks werden bei Threads im Thread-Pool gequeued
„Work-Stealing“: Sobald ein Thread frei ist, übernimmt er Aufgaben von
anderen Threads
Ausführung des Root-Tasks durch Thread-Pool
> java ForkJoinTotalFileSize C:\Users\hpForkJoinTotalFileSizeTotal Size: 83663143456Time taken: 9.15555707
aus: V. Subramaniam: Programming Concurrency on the JVM
SYSTEM SOFTWARE 93
Multithreading
Einleitung und Grundlagen
Multithreading Grundlagen
Probleme bei Multithreading
Klassisches Modell
Synchronisation
Threads und Swing
Neues Modell
Executors und Futures
Synchronisation
Concurrent Collections
Fallbeispiel
Zusammenfassung
SYSTEM SOFTWARE 94
Zusammenfassung
Thread-safe = Programme, die auch bei Ausführung durch mehrere
Threads korrekt funktionieren
Problem ist gemeinsamer veränderlicher Speicher
Maßnahmen
kein (oder stark eingeschränkter) gemeinsamer veränderbarer Speicher
Ausführung bestimmer Teile in einem Thread (single thread rule)
synchronisierte Zugriffe
Gute Lösung oft: eingeschränkter gemeinsamer Speicher
und unveränderbare Datenstrukturen
Seit Verion 1.5 bei Java neues Threading-Konzept
Executors und ThreadPools
Futures und Callables
neue Synchronisationsmechanismen (Locks, Latch, ...)
Concurrent Collections
Wichtig bei hoch asynchronen Prozessen
SYSTEM SOFTWARE 95
Literatur
Doug Lea: Concurrent Programming in Java: Design Principles and Pattern
(2nd Edition) . A comprehensive work by a leading expert, who's also the
architect of the Java platform's concurrency framework.
Brian Goetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes, and
Doug Lea: Java Concurrency in Practice. A practical guide designed to be
accessible to the novice.
Jeff Magee and Jeff Kramer: Concurrency: State Models & Java Programs
(2nd Edition). An introduction to concurrent programming through a
combination of modeling and practical examples.
Venkat Subramaniam: Programming Concurrency on the JVM. The Pragmatic
Bookshelf, 2011. Introduction to concurrency, software transactional
memory and actors in Java.