Parallele Programmierung (3IB)
=> Grundlage: Hettel und Tran (2016, Kap. 3 und 4)
volatile
)synchronized
-Methoden und -Blöcke und “Lock-Objekte” (Monitor)Vector
public class RamTest extends Thread {
private final int i;
public RamTest(int i) {
this.i = i;
}
public void print(int i) {
var a = i * i;
var b = Integer.valueOf(a);
System.out.println(b);
}
@Override
public void run() {
print(this.i);
}
public static void main(String... args) {
new Thread(new RamTest(2)).start();
new Thread(new RamTest(3)).start();
}
}
Fall 1:
t3: temp = 1
t3: temp = 1+3
t3: c.counter = 4
t4: temp = 4
t4: temp = 4+4
t4: c.counter = 8
\(\to\) 8
Fall 1:
t3: temp = 1
t3: temp = 1+3
t3: c.counter = 4
t4: temp = 4
t4: temp = 4+4
t4: c.counter = 8
\(\to\) 8
Fall 2:
t3: temp = 1
t3: temp = 1+3
t4: temp = 1
t3: c.counter = 4
t4: temp = 1+4
t4: c.counter = 5
\(\to\) 5
Fall 1:
t3: temp = 1
t3: temp = 1+3
t3: c.counter = 4
t4: temp = 4
t4: temp = 4+4
t4: c.counter = 8
\(\to\) 8
Fall 2:
t3: temp = 1
t3: temp = 1+3
t4: temp = 1
t3: c.counter = 4
t4: temp = 1+4
t4: c.counter = 5
\(\to\) 5
Fall 3:
t3: temp = 1
t4: temp = 1
t3: temp = 1+3
t4: temp = 1+4
t4: c.counter = 5
t3: c.counter = 4
\(\to\) 4
Möglichkeit unterschiedlicher Traces: Race Condition (auch: Data Race)
kritische Abschnitte (engl. “critical regions”)
Im vorigen Beispiel sind die Methoden set
und add
kritische Abschnitte.
Sie schließen sich gegenseitig und untereinander aus.
gegenseitiger Ausschluss = “mutual exclusion”, verkürzt “mutex”
volatile
gekennzeichnet).Threads greifen immer auf eine lokale Kopie des Heaps zu!
volatile
synchronized
synchronized
-Blocksynchronized
-BlocksFolgende Fälle bilden Memory Barriers, an denen die Cache-lokalen Änderungen zwischen Threads ausgetauscht werden:
volatile
-Variablensynchronized
Folgende Fälle bilden Memory Barriers, an denen die Cache-lokalen Änderungen zwischen Threads ausgetauscht werden:
0
, false
oder null
) aller Variablen sorgt für die Synchronisierung mit dem ersten Zugriff.join()
-Aufruf zurückkehrt, sieht der Aufrufer alle von dem Thread gemachten Änderungen.isInterrupted()
liefert immer den aktuellen Unterbrechungsstatus.pp.02.01-MemoryBarrier
Besonderheit: threadlokaler Speicher (TLS)
T
, wenn über ThreadLocal<T>
erzeugtT
Integer
public class ThreadLocalDemo {
public static class Runner implements Runnable {
public static ThreadLocal<Integer> mem =
new ThreadLocal<>() {
@Override
protected Integer initialValue() {
return Integer.valueOf(1);
}
};
@Override
public void run() {
while (true) {
mem.set(mem.get() + 1);
}
}
}
public static void main(String... args) {
var runnable = new Runner();
new Thread(runnable, "Runner-1").start();
new Thread(runnable, "Runner-2").start();
}
}
Man kann einen threadlokalen Zufallszahlengenerator abrufen. Dieser ist nicht threadsicher implementiert und damit effizienter als der “normale” Zufallszahlengenerator java.util.Random
, der threadsicher implementiert ist. ThreadLocalRandom
aus dem Package java.util.concurrent
hat aber dieselbe Signatur wie Random
:
pp.02.02-ThreadLocal
synchronized
in Methoden-Signatursynchronized
-Methoden eines Objekts gemeinsamsynchronized
-Block fungiert gleichzeitig als Memory Barrier (wichtig für Getter und Setter)synchronized
-Block
this
private final Object lock = new Object();
public void doubler() {
synchronized (this.lock) {
this.counter = this.counter * 2;
}
}
static
ist (Klassenmethode), wird die Klasse selber als Monitor verwendetpp.02.03-Lock
StringBuffer
, Vector
und HashTable
:
synchronized
StringBuilder
, ArrayList
, LinkedList
, HashMap
, IdentityHashMap
, TreeMap
synchronized
private static Vector<Integer> vec = new Vector<>();
public static void main(String... args)
throws InterruptedException {
var remover = new Thread(() -> {
for (var i = 0; i < vec.size(); i++) {
if ((vec.get(i) % 2) == 1) {
vec.remove(i);
}
}
}, "Odd-Remover");
var adder = new Thread(() -> {
for (var i = 0; i < MAX; i++) {
vec.add(i);
}
}, "Adder");
remover.start(); adder.start();
remover.join(); adder.join();
public class Deadlock {
private static Object l1 = new Object();
private static Object l2 = new Object();
public static void m1() {
synchronized (l1) {
synchronized (l2) {
/* ... */
}
}
}
public static void m2() {
synchronized (l2) {
synchronized (l1) {
/* ... */
}
}
}
public static void main(String... args) {
var t1 = new Thread(Deadlock::m1); t1.start();
var t2 = new Thread(Deadlock::m2); t2.start();
}
}
public class Deadlock {
private static Object l1 = new Object();
private static Object l2 = new Object();
public static void m1() {
synchronized (l1) {
synchronized (l2) {
/* ... */
}
}
}
public static void m2() {
synchronized (l2) {
synchronized (l1) {
/* ... */
}
}
}
public static void main(String... args) {
var t1 = new Thread(Deadlock::m1); t1.start();
var t2 = new Thread(Deadlock::m2); t2.start();
}
}