Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Unter der Haube der JVM - Das Java Speichermodell in nebenläufigen Programmen

Unter der Haube der JVM - Das Java Speichermodell in nebenläufigen Programmen

Beschreibt grundlegende Probleme der nebenläufigen Programmierung und Mechanismen und Patterns der JVM, diese zu umgehen oder zu lösen.

Christian Schmidt

November 14, 2019
Tweet

More Decks by Christian Schmidt

Other Decks in Programming

Transcript

  1. Wer bin ich? Christian Iwanzik (34) Softwareentwickler Dipl-Inf. (FH) -

    FH Köln Steckenpferde: • JVM • Kotlin • Scala • auch java ;-) • Domain Driven Design • Microservicearchitekturen Seit 2010 bei tarent Solutions in Bonn Schmidt
  2. ? Immutability Futures ReentrantLock Concurrency final synchronized volatile Promise Thread

    Lock Atomicity Visibility Ordering Cache race condition starvation Nebenläufige programmierung...
  3. Hardware Memory Model Main Memory L3 Cache L2 Cache CPU

    Core L2 Cache L2 Cache L1 Cache L1 Cache L1 Cache CPU Core CPU Core Das Hardwaremodell
  4. Java Memory Model Heap Thread Stack Thread Thread Stack Thread

    Stack Thread Thread Cache Das Java Memory Model
  5. Thread 1 Thread 2 CPU Cache CPU Cache Heap bus

    flush refresh Das Java Memory Model
  6. • Atomicity • Atomicity • Visibility • Ordering class JmmPitfalls

    { long number = 0; boolean flag = false; @Thread1 void write() { number = 999999999999L; flag = true; } @Thread2 void read() { if(flag) { System.out.println(number); } } } Pitfalls
  7. Thread 1 Thread 2 Cache Heap bus Atomicity x=999999...L println(x)

    Cache Ausgabe: • 0 • 232-1 • 264-1 long number = 0; ... number = 999999999999L 2 * 32 Bit Zuweisungen flush refresh Pitfalls - Atomicity
  8. class JmmPitfalls { long number = 0; boolean flag =

    false; @Thread1 void write() { number = 999999999999L; flag = true; } @Thread2 void read() { if(flag) { System.out.println(number); } } } • Visibility • Atomicity • Visibility • Ordering Pitfalls
  9. Thread 1 Thread 2 Cache Heap bus Visibility (Sequential Consistency)

    flag = true println(flag) Cache Ausgabe: • false letzter flush Pitfalls - Visibility
  10. class JmmPitfalls { long number = 0; boolean flag =

    false; @Thread1 void write() { number = 999999999999L; flag = true; } @Thread2 void read() { if(flag) { System.out.println(number); } } } • Ordering • Atomicity • Visibility • Ordering Pitfalls
  11. Thread 1 Thread 2 Cache Heap bus Ordering x=999999...L flag=true

    println(x) println(flag) Cache Ausgabe: • 0 • true flush partieller refresh boolean flag = false; ... flag = true; Pitfalls - Ordering
  12. class FinalConcurrent { final int x; // <- X is

    final int y; // <- Y not // not thread safe! public FinalConcurrent() { x = 3; y = 4; } } class ConcurentUsage { FinalConcurrent f; @Thread1 void writer() { f = new FinalConcurrent(); } @Thread2 void reader() { if(f != null) { int i = f.x; // <- Das JMM garantiert den Wert 3! int y = f.y; // <- `y` kann hier 0 oder 4 sein. } } } flush final refresh Synchronisierung - final
  13. volatile class Volatile { long number; volatile boolean flag; @Thread1

    void write() { number = 999999999999L; flag = true; } @Thread2 void read() { if(flag) { System.out.println(number); } } } flush volatile gilt nur für primitive Werte und Objektreferenzen. Nicht für die Objekte selbst! refresh Synchronisierung - volatile
  14. Thread 1 Thread 2 A B 1 2 Object x

    = new Object; Lock on X unlock Lock on X Wait on X 3 4 C refresh flush refresh Synchronisierung - synchronized
  15. @Thread1 synchronized void pushSynced(String name) { expensiveOperation(); sharedState.add(name); } @Thread2

    synchronized String popSynced() { if (!sharedState.isEmpty()) { return sharedState.remove(sharedState.size() - 1); } else { return "n/a"; } } private final List<String> sharedState = new ArrayList<>(); Blockiert alle zugreifenden Threads Lock ist `this` Synchronisierung - synchronized
  16. private final Object lock = new Object(); @Thread2 public String

    popSyncedLocal() { synchronized (lock) { if (!namesSharedState.isEmpty()) { return namesSharedState.remove(...); } else { return "n/a"; } } } @Thread1 public void pushSyncedLocal(String name) { expensiveOperation(); synchronized (lock) { namesSharedState.add(name); } } Blockiert nicht Eigenes Lockobjekt explizites locking Synchronisierung - synchronized
  17. public class Person { public final String firstname; public final

    String lastname; public final LocalDate birthDate; public Person (String firstname, String lastname, LocalDate birthDate) { … } public Person birthday() { return new Person( firstname, lastname, birthDate.plusYears(1) ); } } Objekte müssen immutable sein. “Verändernde” Methoden liefern Kopien. Funktioniert gut in Verbindung mit volatile. Patterns - Immutable Objects
  18. volatile Person person; public void set(Person person) { this.person =

    person; } public void setBirthday(Person person) { this.person = person.birthday(); } public Person get() { return this.person; } `volatile` liefert die threadsicherheit für die Referenz. `Person` liefert die threadsicherheit durch “Immutability”. Patterns - Immutable Objects
  19. Future<Person> yearOlderFuturePerson = someFuturePerson .thenApply(person -> person.birthday()); Person person =

    yearOlderFuturePerson.get(); ScheduledExecutorService executorService = Executors.newScheduledThreadPool(128); CompletableFuture<Person> someFuturePerson = CompletableFuture.supplyAsync( () -> { try { Thread.sleep(400); return new Person("Hans", "Dampf", LocalDate.of(1983, 6, 14)); } catch (InterruptedException e) { ... } }, executorService); Läuft in einem Thread Wird erst später, bei einem Ergebnis ausgeführt Erst hier wird geblockt Patterns - Futures
  20. AtomicLong aLong = new AtomicLong(42L); return aLong.incrementAndGet(); List<String> list =

    new CopyOnWriteArrayList<>(); ReentrantReadWriteLock reentrantLock = new ... readWriteLock.readLock(); readWriteLock.writeLock(); Semaphore semaphore = new Semaphore(3); semaphore.acquire(); semaphore.acquire(); semaphore.acquire(); semaphore.acquire(); // blocks! ReentrantLock reentrantLock = new ... reentrantLock.lock(); // Make some synchronized stuff reentrantLock.unlock(); new UnmodifiableList(someMutableList); Patterns - Viele kleine Helferlein
  21. Andere Sprachen - Kotlin data class FirstName(val value: String) data

    class Employee(val firstName: FirstName, val lastName: LastName) val max = Employee(FirstName("Max"), LastName("Mustermann")) val updatedEmployee = max.copy(lastName = LastName("Musterfrau")) Data classes val deferred: Deferred<Int> = GlobalScope.async { delay(1000) println("Async") 13 + 29 } val result: Int = runBlocking { deferred.await() } Coroutines val list: List<String> = listOf("blue", "green", "red") val newList: List<String> = list + "yellow" Immutable Collection
  22. Andere Sprachen - Javascript/node.js let promise = new Promise((resolve, reject)

    => { setTimeout(() => resolve(24), 1000); }); promise.then(result => { console.log(result); }); promise.catch(error => { console.error(error); }); Promise async function myAsync() { let result = await promise; return result; } async function app() { console.log(await myAsync()); } module.exports=app; Async/Await
  23. Andere Sprachen - Go messages := make(chan string) go func()

    { messages <- "ping" }() msg := <-messages fmt.Println(msg) Go-Routine und Channels
  24. • Nutze immutable Objects und copy-on-write, um gemeinsame Nutzung zu

    verhindern. • Identifiziere gemeinsam genutzte Objekte. • Schütze diese Objekte, und nur diese, mit Synchronisierung. • Bevor du einen eigenen Thread startest, prüfe, ob auch ein Threadpool hilft. Grundsätze
  25. Vielen Dank! tarent solutions GmbH Rochusstraße 2-4 53123 Bonn Telefon:

    0228 54881-0 Telefax: 0228 54881-235 [email protected] www.tarent.de/academy Christian Schmidt Softwareentwickler [email protected] @chris__schmidt github.com/DarkToast