SlideShare ist ein Scribd-Unternehmen logo
1 von 158
Mein paralleles Leben
als Java-Programmierer
Johannes Link
 johanneslink.net
Stephan Mosel - http://www.flickr.com/photos/moe/9474926/
2000
public class MyClass {
	   private volatile String myProp = "";
	   public synchronized String getMyProp() {
	   	   return myProp;
	   }
	   public void setMyProp(String value) {
	   	   synchronized (this) {
	   	   	   myProp = value;
	   	   	   this.notifyAll();
	   	   }
	   }
	   public synchronized void waitUntilPropNotEmpty()
                              throws InterruptedException {
	   	   while (myProp.equals("")) {
	   	   	   this.wait();
	   	   }
	   }
}
public class MyClassTest...
	   private volatile boolean succeeded = false;
	   @Test public void waitingForAValue() throws Exception {
	   	   final MyClass my = new MyClass();
	   	   Thread myThread = new Thread(new Runnable() {
	   	   	   public void run() {
	   	   	   	   try {
	   	   	   	   	   my.waitUntilPropNotEmpty();
	   	   	   	   	   succeeded = true;
	   	   	   	   } catch (InterruptedException e) {
	   	   	   	   	   Thread.currentThread().interrupt();
	   	   	   	   }
	   	   	   }
	   	   });
	   	   myThread.start();
	   	   my.setMyProp("");
	   	   assertFalse(succeeded);
	   	   my.setMyProp("test");
	   	   myThread.join();
	   	   assertTrue(succeeded);
	   }
Angenehme Position des
     Halbwissens
Angenehme Position des
      Halbwissens
"Nebenläufigkeit wird
völlig überbewertet"
Angenehme Position des
      Halbwissens
"Nebenläufigkeit wird
völlig überbewertet"
                       "Das erledigen die
                       Frameworks für mich"
Angenehme Position des
      Halbwissens
"Nebenläufigkeit wird
völlig überbewertet"
                       "Das erledigen die
                       Frameworks für mich"

 "Ich trenne meine Fachlogik von
 Nebenläufigkeit und Threading"
Angenehme Position des
      Halbwissens
"Nebenläufigkeit wird
völlig überbewertet"
                       "Das erledigen die
                       Frameworks für mich"

 "Ich trenne meine Fachlogik von
 Nebenläufigkeit und Threading"

               "Ich denke mal ganz intensiv
               darüber nach und dann
               funktioniert das"
Kein kostenloses
Mittagessen mehr...
aus Herb Sutter: "The free lunch is over"
"Applications will increasingly need to be
  concurrent if they want to fully exploit
 continuing exponential CPU throughput
                  gains."
                               Herb Sutter
Was ist das Problem?
Was ist das Problem?


• Safety:
  Nothing bad ever happens
Was ist das Problem?


• Safety:
  Nothing bad ever happens
• Liveness:
  Something good eventually happens
Was ist das Problem?


• Safety:
  Nothing bad ever happens
• Liveness:
  Something good eventually happens
• Performance:
  Things happen faster
Safety - Alles ist korrekt
public class Counter {
	   private long count = 0;
	   public long value() {
	   	   return count;
	   }
	   public void inc() {
	   	   count++;
	   }
}

Counter counter = new Counter();
//in Thread A:
counter.inc();
//in Thread B:
counter.inc();
//danach:
assert counter.value() == 2;
Safety - Alles ist korrekt
public class Counter {
	   private long count = 0;
	   public long value() {          counter.inc():
	   	   return count;
	   }
	   public void inc() {            temp = counter.count
	   	   count++;
	   }
}                                  temp = temp + 1

Counter counter = new Counter();
//in Thread A:                     counter.count = temp
counter.inc();
//in Thread B:
counter.inc();
//danach:
assert counter.value() == 2;
Safety - Alles ist korrekt
public class Counter {
	   private long count = 0;
	   public long value() {
	   	   return count;
	   }
	   public void inc() {
	   	   count++;
	   }
}

Counter counter = new Counter();
//in Thread A:
counter.inc();
//in Thread B:
counter.inc();
//danach:
assert counter.value() == 2;
Safety - Alles ist korrekt
public class Counter {             Thread A:      Thread B:
	   private long count = 0;
	   public long value() {          temp = 0
	   	   return count;
	   }
	   public void inc() {            temp = 0 + 1
	   	   count++;
	   }
}                                  count = 1

Counter counter = new Counter();                  temp = 1
//in Thread A:
counter.inc();
                                                  temp = 1 + 1
//in Thread B:
counter.inc();
//danach:                                         count = 2
assert counter.value() == 2;
Safety - Alles ist korrekt
public class Counter {
	   private long count = 0;
	   public long value() {
	   	   return count;
	   }
	   public void inc() {
	   	   count++;
	   }
}

Counter counter = new Counter();
//in Thread A:
counter.inc();
//in Thread B:
counter.inc();
//danach:
assert counter.value() == 2;
Safety - Alles ist korrekt
public class Counter {             Thread A:      Thread B:
	   private long count = 0;
	   public long value() {          temp = 0
	   	   return count;
	   }
	   public void inc() {            temp = 0 + 1
	   	   count++;
	   }
}                                                 temp = 0

Counter counter = new Counter();
                                   count = 1
//in Thread A:
counter.inc();
                                                  temp = 0 + 1
//in Thread B:
counter.inc();
//danach:                                         count = 1
assert counter.value() == 2;
Thread A:   Thread B:
Step A.1    Step B.1


Step A.2

            Step B.2

Step A.3
            Step B.3



Step A.4    Step B.4




Step A.5
Thread A:   Thread B:
Step A.1    Step B.1


Step A.3

            Step B.3

Step A.2
            Step B.2



Step A.5    Step B.4




Step A.4
Was bedeutet korrekt
      in einem
   nebenläufigen
    Programm?
Synchronisation
 nebenläufiger Objekte


Bestimmte Programmbereiche werden
durch Schlösser (Lock) in eine definierte
sequenzielle Reihenfolge gebracht
Locks
public class LockedCounter {       Thread A:          Thread B:
	   private long count = 0;
	   private Lock lock              lock.lock()
          = new ReentrantLock();
	   public long value() {
	   	   lock.lock();               temp = 0           lock.lock()
	   	   long value = count;
	   	   lock.unlock();                           muss warten
	   	   return value;
	   }
                                   lock.unlock()
	   public void inc() {
	   	   lock.lock();                                  temp = 1
	   	   count++;
	   	   lock.unlock();
	   }
}                                                     count = 2


                                                      lock.unlock()
Locks sind nicht umsonst
Locks sind nicht umsonst

• Sie bergen das Risiko von Deadlocks
Locks sind nicht umsonst

• Sie bergen das Risiko von Deadlocks
• Sie führen zu sequenzialisierter
  Programmausführung
Locks sind nicht umsonst

• Sie bergen das Risiko von Deadlocks
• Sie führen zu sequenzialisierter
  Programmausführung
• Sie benötigen zusätzliche
  Prozessorzyklen
  ‣ Basieren auf primitiven Operationen (TAS
    oder CAS), welche (oft) auf "richtiges"
    Memory zugreifen müssen
Caching, Memory & Co
         Prozessor        Prozessor
      Core       Core
    L1-Cache   L1-Cache

         L2-Cache


                Memory
Caching, Memory & Co
           Prozessor        Prozessor
        Core       Core
1-2
      L1-Cache   L1-Cache

           L2-Cache


                  Memory
Caching, Memory & Co
                 Prozessor        Prozessor
              Core       Core
20-30 1-2
            L1-Cache   L1-Cache

                 L2-Cache


                        Memory
Caching, Memory & Co
                  Prozessor        Prozessor
               Core       Core
 20-30 1-2
             L1-Cache   L1-Cache

                  L2-Cache

300-400
 Zyklen
                         Memory
Wie machen wir ein
 Objekt für andere
Threads sichtbar?
Prozessor
  Core       Core
L1-Cache   L1-Cache

     L2-Cache


     Memory
Prozessor

Unshared     Core       Core
 Object    L1-Cache   L1-Cache

                L2-Cache


                Memory
Prozessor

 Unshared         Core       Core
  Object        L1-Cache   L1-Cache

                     L2-Cache
Shared Object


                     Memory
Sicheres Publizieren -
     Safe Publication

• Initialisierung in statischem Initializer
• Speichern in volatile-Feld oder in
  AtomicReference
• Ein unveränderliches Objekt
• Speichern in einem Feld, das vollständig
  durch einen Lock abgesichert ist
Grundsätze für die
 Verwendung von Locks
Halte genau dann einen Lock,
Grundsätze für die
 Verwendung von Locks
Halte genau dann einen Lock,
• wenn du auf gemeinsamen und veränder-
  lichen Zustand zugreifst
Grundsätze für die
 Verwendung von Locks
Halte genau dann einen Lock,
• wenn du auf gemeinsamen und veränder-
  lichen Zustand zugreifst
• wenn du atomare Operationen ausführst
   ‣ check then act
   ‣ read-modify-write
Grundsätze für die
  Verwendung von Locks
Halte genau dann einen Lock,
• wenn du auf gemeinsamen und veränder-
  lichen Zustand zugreifst
• wenn du atomare Operationen ausführst
   ‣ check then act
   ‣ read-modify-write
Halte den Lock nicht länger als nötig
Liveness - Jeder sollte an
   die Reihe kommen


• Gefahr 1: Deadlocks
• Gefahr 2: Starvation
Deadlock
public class SimpleDeadlock...
  private final Object left       Thread A:       Thread B:
      = new Object();
  private final Object right      lock left
      = new Object();
 public void leftRight() {                        lock right
   synchronized (left) {
     synchronized (right) {       try to
       doSomething();             lock right
     }                                            try to
   }                                              lock left
 }
 public void rightLeft() {
   synchronized (right) {
     synchronized (left) {        wait for ever
       doSomething();
     }                                            wait for ever
   }
 }
Starvation


Bei hohem "Wettbewerb" (contention) um
die gleiche Ressource (z.B. Lock)
kommen nicht alle wartenden Threads an
die Reihe
Performance

• Mehr Prozessoren/Kerne sollten zu
  besserer Performance führen
  ‣ Besseres Antwortverhalten durch
    Verlagerung lang-laufender Berechnungen
    in parallele Threads
  ‣ Höherer Durchsatz durch Aufteilung von
    Berechnungen in mehrere Threads
Amdahl's Law
Amdahl's Law




F: nicht parallelisierbare Anteil eines Programms
N: Anzahl der Kerne / Prozessoren
F = 0,2
                 5.00




                 3.75
Beschleunigung




                 2.50




                 1.25




                   0
                        1   2   3     4       5      10      20   100
                                Anzahl der Kerne / Prozessoren
Wie parallelisiert man ein
      Programm?
Wie parallelisiert man ein
      Programm?


• Parallelisierung des Kontrollflusses
Wie parallelisiert man ein
      Programm?


• Parallelisierung des Kontrollflusses
• Parallellisierung der Daten
Parallelisierung des Kontrollflusses

Step 1


Step 2


Step 3



Step 4


Step 5



Step 6
Parallelisierung des Kontrollflusses

Step 1           Step 1


Step 2           Step 2              Step 3


Step 3
                 Step 4a   Step 4b    Step 4c


Step 4

                 Step 5              Step 6
Step 5



Step 6
Rekursive Zerlegung des
         Kontrollflusses (Fork/Join)
Step 1


Step 2


Step 3



Step 4


Step 5



Step 6
Rekursive Zerlegung des
         Kontrollflusses (Fork/Join)
Step 1              Step 1


Step 2
                    Steps 2-5

Step 3

                    2-5.1     2-5.2       2-5.n
Step 4

                    2-5.1.1     2-5.1.2           2-5.1.n
Step 5



Step 6
                    Step 6
Parallelisierung der Daten

for 1..n do

  Step 1


  Step 2


  Step 3


  Step 4


  Step 5
Parallelisierung der Daten

for 1..n do      for each element do in parallel:

  Step 1           Step 1
                       Step 1
                           Step 1
                                    Step 1
                                         Step 1
  Step 2           Step 2
                       Step 2
                           Step 2
                                    Step 2
                                         Step 2
  Step 3           Step 3
                       Step 3
                           Step 3
                                    Step 3
                                         Step 3
  Step 4           Step 4
                       Step 4
                           Step 4
                                    Step 4
                                         Step 4
  Step 5           Step 5
                       Step 5
                           Step 5
                                    Step 5
                                         Step 5
java.util.concurrent[.a
     tomic|locks]
Locks und Conditions
  <<Interface>>                   <<Interface>>
       Lock                         Condition
                      creates
lock()                          await()
lockInterruptibly()             await(timeout)
tryLock(timeout)                signal()
unlock()                        signalAll()




  ReentrantLock
public class MyNewClass...
	   private String myProp = "";
	   private Lock lock = new ReentrantLock();
	   private Condition propNotEmpty = lock.newCondition();
	   public String getMyProp() {
	   	   lock.lock();
	   	   try {
	   	   	    return myProp;
	   	   } finally {lock.unlock();}
	   }
	   public void setMyProp(String value) {
	   	   lock.lock();
	   	   try {
	   	   	    myProp = value;
	   	   	    propNotEmpty.signalAll();
	   	   } finally {lock.unlock();}
	   }
	   public void waitUntilPropNotEmpty() throws InterruptedException {
	   	   lock.lock();
	   	   try {
	   	   	    while (myProp.equals("")) {
	   	   	    	   propNotEmpty.await();
	   	   	    }
	   	   } finally {lock.unlock();}
	   }
public class MyNewClass...
	   private String myProp = "";
	   private Lock lock = new ReentrantLock();
	   private Condition propNotEmpty = lock.newCondition();
	   public String getMyProp() {
	   	   lock.lock();
	   	   try {
	   	   	    return myProp;
	   	   } finally {lock.unlock();}
	   }
	   public void setMyProp(String value) {
	   	   lock.lock();
	   	   try {
	   	   	    myProp = value;
	   	   	    propNotEmpty.signalAll();
	   	   } finally {lock.unlock();}
	   }
	   public void waitUntilPropNotEmpty() throws InterruptedException {
	   	   lock.lock();
	   	   try {
	   	   	    while (myProp.equals("")) {
	   	   	    	   propNotEmpty.await();
	   	   	    }
	   	   } finally {lock.unlock();}
	   }
public class MyNewClass...
	   private String myProp = "";
	   private Lock lock = new ReentrantLock();
	   private Condition propNotEmpty = lock.newCondition();
	   public String getMyProp() {
	   	   lock.lock();
	   	   try {
	   	   	    return myProp;
	   	   } finally {lock.unlock();}
	   }
	   public void setMyProp(String value) {
	   	   lock.lock();
	   	   try {
	   	   	    myProp = value;
	   	   	    propNotEmpty.signalAll();
	   	   } finally {lock.unlock();}
	   }
	   public void waitUntilPropNotEmpty() throws InterruptedException {
	   	   lock.lock();
	   	   try {
	   	   	    while (myProp.equals("")) {
	   	   	    	   propNotEmpty.await();
	   	   	    }
	   	   } finally {lock.unlock();}
	   }
Implizite oder explizite
  Locks / Conditions
• Verwende implizite Locks und Conditions,
  wenn dir deren Feature-Set genügt
  ‣ Performance seit Java 6 praktisch gleich
• Verwende explizite Locks und Conditions,
  wenn du mehr brauchst
  ‣ Unterbrechbarkeit
  ‣ tryLock()
  ‣ Timeouts
  ‣ Mehr als eine Condition-Queue
Das Executor-Framework
Nie wieder new Thread(...).start() !

        <<Interface>>
           Executor
                                  <<Interface>>
      execute(Runnable)            Callable<T>
                                 call(): T


       <<Interface>>
      ExecutorService
                                    <<Interface>>
 submit(Runnable):Future<?>
                                      Future<T>
 submit(Callable<T>):Future<T>
 shutdown()                      cancel(mayInterrupt)
 awaitTermination(timeout)       get(): T
                                 get(timeout): T
public class MyNewClassTest...
	   private volatile boolean succeeded = false;
	   @Test
	   public void waitingForAValue() throws Exception {
	   	   final MyNewClass my = new MyNewClass();
	   	   ExecutorService executor = Executors.newCachedThreadPool();
	   	   Runnable task = new Runnable() {
	   	   	    @Override
	   	   	    public void run() {
	   	   	    	   try {
	   	   	    	   	    my.waitUntilPropNotEmpty();
	   	   	    	   	    succeeded = true;
	   	   	    	   } catch (InterruptedException e) {
	   	   	    	   	    Thread.currentThread().interrupt();
	   	   	    	   }
	   	   	    }
	   	   };
	   	   Future<?> result = executor.submit(task);
	   	   my.setMyProp("");
	   	   assertFalse(succeeded);
	   	   my.setMyProp("test");
	   	   result.get(); //wait for end of task
	   	   assertTrue(succeeded);
	   }
public class MyNewClassTest...
	   private volatile boolean succeeded = false;
	   @Test
	   public void waitingForAValue() throws Exception {
	   	   final MyNewClass my = new MyNewClass();
	   	   ExecutorService executor = Executors.newCachedThreadPool();
	   	   Runnable task = new Runnable() {
	   	   	    @Override
	   	   	    public void run() {
	   	   	    	   try {
	   	   	    	   	    my.waitUntilPropNotEmpty();
	   	   	    	   	    succeeded = true;
	   	   	    	   } catch (InterruptedException e) {
	   	   	    	   	    Thread.currentThread().interrupt();
	   	   	    	   }
	   	   	    }
	   	   };
	   	   Future<?> result = executor.submit(task);
	   	   my.setMyProp("");
	   	   assertFalse(succeeded);
	   	   my.setMyProp("test");
	   	   result.get(); //wait for end of task
	   	   assertTrue(succeeded);
	   }
public class MyNewClassTest...
	   private volatile boolean succeeded = false;
	   @Test
	   public void waitingForAValue() throws Exception {
	   	   final MyNewClass my = new MyNewClass();
	   	   ExecutorService executor = Executors.newCachedThreadPool();
	   	   Runnable task = new Runnable() {
	   	   	    @Override
	   	   	    public void run() {
	   	   	    	   try {
	   	   	    	   	    my.waitUntilPropNotEmpty();
	   	   	    	   	    succeeded = true;
	   	   	    	   } catch (InterruptedException e) {
	   	   	    	   	    Thread.currentThread().interrupt();
	   	   	    	   }
	   	   	    }
	   	   };
	   	   Future<?> result = executor.submit(task);
	   	   my.setMyProp("");
	   	   assertFalse(succeeded);
	   	   my.setMyProp("test");
	   	   result.get(); //wait for end of task
	   	   assertTrue(succeeded);
	   }
Atomics
public class SynchronizedCounter...
	   private long count = 0;
	   public synchronized long value() {
	   	   return count;
	   }
	   public synchronized void inc() {
	   	   count++;
	   }
Atomics
public class SynchronizedCounter...
	   private long count = 0;
	   public synchronized long value() {
	   	   return count;
	   }
	   public synchronized void inc() {
	   	   count++;
	   }



public class AtomicCounter...
	   private AtomicLong count = new AtomicLong(0);
	   public long value() {
	   	   return count.longValue();
	   }
	   public void inc() {
	   	   count.incrementAndGet();
	   }
Compare and Set
public class HandmadeAtomicReference<V>...
	   private V ref;
	   public HandmadeAtomicReference(V initialValue) {...}
	   public synchronized V get() { return ref; }
	   /**
	   * Atomically sets the value to the given updated value
	   * if the current value {@code ==} the expected value.
	   * @param expect the expected value
	   * @param update the new value
	   * @return true if successful.	
	   */
	   public synchronized boolean compareAndSet(V expect, V update) {
	   	   if (ref != expect)
	   	   	    return false;
	   	   ref = update;
	   	   return true;
	   }
Und noch mehr...



• Concurrent Collections
• BlockingQueue
• Latches, Barriers, Exchangers & Co
Mein Lieblingsbeispiel:
            Banking
                Bank

createNewAccountFor(customer): Account
accountByNumber(long accNum): Account
allAccountNumbers(): Collection<long>




             Account

  accountNumber: long
  customer: String
  balance: BigDecimal

  deposit(amount)
  withdraw(amount)
Mein Lieblingsbeispiel:
            Banking
                Bank

createNewAccountFor(customer): Account
accountByNumber(long accNum): Account
allAccountNumbers(): Collection<long>
                                             uses



             Account
                                               MoneyTransfer
  accountNumber: long
  customer: String                       amount: BigDecimal
  balance: BigDecimal                    from, to: long

  deposit(amount)                        execute(Bank bank)
  withdraw(amount)
public class Account {
	   private BigDecimal balance = BigDecimal.ZERO;
	   private final long accountNumber;
	   private final String customer;
	   public Account(long accountNumber, String customer) {...}
	   public void deposit(BigDecimal amount) {
	   	   balance = balance.add(amount);
	   }
	   public void withdraw(BigDecimal amount) {
	   	   balance = balance.subtract(amount);
	   }
	   public BigDecimal getBalance() {
	   	   return balance;
	   }
	   public long getAccountNumber() {
	   	   return accountNumber;
	   }
	   public String getCustomer() {
	   	   return customer;
	   }
}
public class Bank {
	   private Map<Long, Account> accounts = new HashMap<Long, Account>();
	   private long lastAccountNumber = 0;

	   public Account createNewAccountForString(String customer) {
	   	   Account account = new Account(++lastAccountNumber, customer);
	   	   accounts.put(account.getAccountNumber(), account);
	   	   return account;
	   }

	   public Account accountByNumber(long accountNumber) {
	   	   return accounts.get(accountNumber);
	   }
}
public class AccountCreationTest extends ParallelTestCase...
	   private volatile Bank bank = new Bank();
	   @Test
	   public void createAccountsInManyThreads() throws Exception {
	   	   final int numberOfAccounts = 100;
	   	   final AtomicInteger account = new AtomicInteger(0);
	   	   Runnable createAccountTask = new Runnable() {
	   	   	    @Override
	   	   	    public void run() {
	   	   	    	   String customer = "c" + account.incrementAndGet();
	   	   	    	   long accountNumber = bank.createNewAccountForString(
                                             customer).getAccountNumber();
	   	   	    	   assertEquals(customer, bank.accountByNumber(
                                             accountNumber).getCustomer());
	   	   	    }
	   	   };
	   	   runInParallelThreads(numberOfAccounts, createAccountTask);
	   	   assertAllAccountsHaveBeenCreated(numberOfAccounts);
	   }
"No account with number 99"
public class Bank {
	   private Map<Long, Account> accounts = new HashMap<Long, Account>();
	   private long lastAccountNumber = 0;

	   public Account createNewAccountForString(String customer) {
	   	   Account account = new Account(++lastAccountNumber, customer);
	   	   accounts.put(account.getAccountNumber(), account);
	   	   return account;
	   }

	   public Account accountByNumber(long accountNumber) {
	   	   return accounts.get(accountNumber);
	   }
}
public class Bank {
	   private Map<Long, Account> accounts = new HashMap<Long, Account>();
	   private AtomicLong lastAccountNumber = new AtomicLong(0L);

	   public Account createNewAccountForString(String customer) {
	   	   Account account =
             new Account(lastAccountNumber.incrementAndGet(), customer);
	   	   accounts.put(account.getAccountNumber(), account);
	   	   return account;
	   }

	   public Account accountByNumber(long accountNumber) {
	   	   return accounts.get(accountNumber);
	   }
}
NullPointerException
public class Bank {
	   private Map<Long, Account> accounts = new HashMap<Long, Account>();
	   private AtomicLong lastAccountNumber = new AtomicLong(0L);

	   public Account createNewAccountForString(String customer) {
	   	   Account account =
             new Account(lastAccountNumber.incrementAndGet(), customer);
	   	   accounts.put(account.getAccountNumber(), account);
	   	   return account;
	   }

	   public Account accountByNumber(long accountNumber) {
	   	   return accounts.get(accountNumber);
	   }
}
public class Bank {
	   private final Map<Long, Account> accounts =
                       new ConcurrentHashMap<Long, Account>();
	   private final AtomicLong lastAccountNumber = new AtomicLong(0L);

	   public Account createNewAccountForString(String customer) {
	   	   Account account =
             new Account(lastAccountNumber.incrementAndGet(), customer);
	   	   accounts.put(account.getAccountNumber(), account);
	   	   return account;
	   }

	   public Account accountByNumber(long accountNumber) {
	   	   return accounts.get(accountNumber);
	   }
}
ok
public class Bank {
	   private final Map<Long, Account> accounts =
                       new ConcurrentHashMap<Long, Account>();
	   private final AtomicLong lastAccountNumber = new AtomicLong(0L);

	   public Account createNewAccountForString(String customer) {
	   	   Account account =
             new Account(lastAccountNumber.incrementAndGet(), customer);
	   	   accounts.put(account.getAccountNumber(), account);
	   	   return account;
	   }

	   public Account accountByNumber(long accountNumber) {
	   	   return accounts.get(accountNumber);
	   }
}
public class Account...
	   private final AtomicReference<BigDecimal> balance =
            new AtomicReference<BigDecimal>(BigDecimal.ZERO);
	   public void deposit(BigDecimal amount) {
	   	   while (true) {
	   	   	    BigDecimal oldAmount = balance.get();
	   	   	    BigDecimal newAmount = oldAmount.add(amount);
	   	   	    if (balance.compareAndSet(oldAmount, newAmount))
	   	   	    	   return;
	   	   }
	   }
	   public void withdraw(BigDecimal amount) {
	   	   while (true) {
	   	   	    BigDecimal oldAmount = balance.get();
	   	   	    BigDecimal newAmount = oldAmount.subtract(amount);
	   	   	    if (balance.compareAndSet(oldAmount, newAmount))
	   	   	    	   return;
	   	   }
	   }
	   public BigDecimal getBalance() {
	   	   return balance.get();
	   }
public class Account...
	   private final AtomicReference<BigDecimal> balance =
            new AtomicReference<BigDecimal>(BigDecimal.ZERO);
	   public void deposit(BigDecimal amount) {
	   	   while (true) {
	   	   	    BigDecimal oldAmount = balance.get();
	   	   	    BigDecimal newAmount = oldAmount.add(amount);
	   	   	    if (balance.compareAndSet(oldAmount, newAmount))
	   	   	    	   return;
	   	   }
	   }
	   public void withdraw(BigDecimal amount) {
	   	   while (true) {
	   	   	    BigDecimal oldAmount = balance.get();
	   	   	    BigDecimal newAmount = oldAmount.subtract(amount);
	   	   	    if (balance.compareAndSet(oldAmount, newAmount))
	   	   	    	   return;
	   	   }
	   }
	   public BigDecimal getBalance() {
	   	   return balance.get();
	   }
public class Account...

	   private BigDecimal balance = BigDecimal.ZERO;

	   public synchronized void deposit(BigDecimal amount) {
	   	   balance = balance.add(amount);
	   }

	   public synchronized void withdraw(BigDecimal amount) {
	   	   balance = balance.subtract(amount);
	   }

	   public synchronized BigDecimal getBalance() {
	   	   return balance;
	   }
public class MoneyTransfer {

	   private final BigDecimal amount;
	   private final long from;
	   private final long to;

	   public MoneyTransfer(BigDecimal amount, long from, long to) {
	   	   this.amount = amount;
	   	   this.from = from;
	   	   this.to = to;
	   }

	   public void execute(Bank bank) {
	   	   Account source = bank.accountByNumber(from);
	   	   Account target = bank.accountByNumber(to);
	   	   if (source.getBalance().compareTo(amount) < 0)
	   	   	    throw new InsufficientFundsException();
	   	   source.withdraw(amount);
	   	   target.deposit(amount);
	   }
}
public class MoneyTransfer {

	   private final BigDecimal amount;
	   private final long from;
	   private final long to;

	   public MoneyTransfer(BigDecimal amount, long from, long to) {
	   	   this.amount = amount;
	   	   this.from = from;
	   	   this.to = to;
	   }

	          synchronized bank) {
    public void execute(Bank void execute(Bank bank) {




                                                             (
	   	   Account source = bank.accountByNumber(from);
	   	   Account target = bank.accountByNumber(to);
	   	   if (source.getBalance().compareTo(amount) < 0)
	   	   	    throw new InsufficientFundsException();
	   	   source.withdraw(amount);
	   	   target.deposit(amount);
	   }
}
public class MoneyTransfer...
	   public void execute(Bank bank) {
	   	   Account source = bank.accountByNumber(from);
	   	   Account target = bank.accountByNumber(to);
	   	   synchronized (source) {
	   	   	    synchronized (target) {
	   	   	    	   doTransfer(source, target);
	   	   	    }
	   	   }
	   }
	   private void doTransfer(Account source, Account target) {
	   	   if (source.getBalance().compareTo(amount) < 0)
	   	   	    throw new InsufficientFundsException();
	   	   source.withdraw(amount);
	   	   target.deposit(amount);
	   }
public class MoneyTransfer...
	   public void execute(Bank bank) {
	   	   Account source = bank.accountByNumber(from);
	   	   Account target = bank.accountByNumber(to);
	   	   synchronized (source) {
	   	   	    synchronized (target) {
	   	   	    	   doTransfer(source, target);




                                                                (
	   	   	    }
	   	   }
	   }
	   private void doTransfer(Account source, Account target) {
	   	   if (source.getBalance().compareTo(amount) < 0)
	   	   	    throw new InsufficientFundsException();
	   	   source.withdraw(amount);
	   	   target.deposit(amount);
	   }



// Thread A:
new MoneyTransfer(new BigDecimal(2), 234, 789).execute(bank);

// Thread B:
new MoneyTransfer(new BigDecimal(2), 789, 234).execute(bank);
public class MoneyTransfer...
	   public void execute(Bank bank) {
	   	   Account source = bank.accountByNumber(from);
	   	   Account target = bank.accountByNumber(to);
	   	   Object[] locks = new Object[] { source, target };
	   	   Arrays.sort(locks);
	   	   synchronized (locks[0]) {
	   	   	    synchronized (locks[1]) {
	   	   	    	   doTransfer(source, target);
	   	   	    }
	   	   }
	   }




public class Account implements Comparable<Account>...
	   public int compareTo(Account other) {
	   	   return new Long(accountNumber).
                      compareTo(new Long(other.accountNumber));
	   }
Für den normal sterblichen
Entwickler ist die Erstellung
  korrekter Multi-Thread-
Programme außerhalb ihrer
       Fähigkeiten
public class ValueHolder {
	   private List<Listener> listeners = new LinkedList<Listener>();
	   private int value;

	     public static interface Listener {
	     	   public void valueChanged(int newValue);
	     }

	     public void addListener(Listener listener) {
	     	   listeners.add(listener);
	     }

	     public void setValue(int newValue) {
	     	   value = newValue;
	     	   for (Listener each : listeners) {
	     	   	    each.valueChanged(newValue);
	     	   }
	     }
}



                   The Problem with Threads
    http://www.eecs.berkeley.edu/Pubs/TechRpts/2006/EECS-2006-1.pdf
Zugrundeliegende
    Programmiermodell


Shared mutable state (aka objects)
is accessed in multiple concurrent threads,
and we use locks to synchronize /
sequentialize access to the state
Das Objekt wird zur
 "undichten" Abstraktion

Um aus einzelnen thread-sicheren Objekten
komplexere thread-sichere Objekte zu
bauen, muss der Locking-Mechanismus der
Einzelobjekte bekannt sein
   ‣ Verletzt Prinzip der Kapselung
   ‣ Verletzt Prinzip der Modularisierung
Können uns andere
Programmierparadigmen
        retten?
Alternative Paradigmen


• Transactional Memory
• Immutability
• Message Passing
• Parallel Collection Processing
• Dataflow Concurrency
Transactional Memory
         (TM)
• Heap als transaktionale Datenmenge
• Transaktionseigenschaften ähnlich wie
  bei einer Datenbank (ACID)
• Optimistische Transaktionen
• Transaktionen werden bei einer
  Kollision automatisch wiederholt
• Transaktionen können geschachtelt und
  komponiert werden
public class Account...
	   public void deposit(BigDecimal amount) {
	   	   atomic {
	   	   	    balance = balance.add(amount);
	   	   }
	   }
	   public void withdraw(BigDecimal amount) {
	   	   atomic {
	   	   	    balance = balance.subtract(amount);
	   	   }
	   }
public class Account...
	   public void deposit(BigDecimal amount) {
	   	   atomic {
	   	   	    balance = balance.add(amount);
	   	   }
	   }
	   public void withdraw(BigDecimal amount) {
	   	   atomic {
	   	   	    balance = balance.subtract(amount);
	   	   }
	   }
public class Account...
	   public void deposit(BigDecimal amount) {
	   	   atomic {
	   	   	    balance = balance.add(amount);
	   	   }
	   }
	   public void withdraw(BigDecimal amount) {
	   	   atomic {
	   	   	    balance = balance.subtract(amount);
	   	   }
	   }


public class MoneyTransfer...
	   public void execute(Bank bank) {
	   	   atomic {
	   	   	    Account source = bank.accountByNumber(from);
	   	   	    Account target = bank.accountByNumber(to);
	   	   	    if (source.getBalance().compareTo(amount) < 0)
	   	   	    	   throw new InsufficientFundsException();
	   	   	    source.withdraw(amount);
	   	   	    target.deposit(amount);
	   	   }
	   }
Eigenschaften von TM
• Vorteile:
  ‣ Wir können weiter in shared state und
    Transaktionen denken
  ‣ Die korrekte Verwendung ist wesentlich
    einfacher als bei Locks: Deadlocks sind
    ausgeschlossen!
• Nachteile:
  ‣ Keinerlei Hilfe, wie wir unser sequenzielles
    Programm parallelisieren
  ‣ Der Fortschritt ist nicht garantiert. Livelocks
    sind möglich
  ‣ Die performante Implementierung ist noch
    Forschungsthema
TM-Implementierungen


• Hardware Transactional Memory
• Software Transactional Memory
 ‣ Clojure: Programmiersprache auf der JVM,
   die STM eingebaut hat
 ‣ Java-STM-Frameworks:
   Deuce, Akka, Multiverse
Immutability
Immutability

• Ohne veränderlichen Zustand, müssen
  Veränderungen auch nicht
  synchronisiert werden
Immutability

• Ohne veränderlichen Zustand, müssen
  Veränderungen auch nicht
  synchronisiert werden
• Systeme haben aber doch einen Zustand
  der sich ändert, oder?
Immutability

• Ohne veränderlichen Zustand, müssen
  Veränderungen auch nicht
  synchronisiert werden
• Systeme haben aber doch einen Zustand
  der sich ändert, oder?
• Trick: Wir unterscheiden zwischen
  unveränderlichen Werten (values) und
  einer bestimmten Entity zugeordnenten
  aktuellen Wert
@Immutable
class Account {
    BigDecimal balance
    long accountNumber
    String customer

    static Account create(long accountNumber, String customer) {
        new Account(balance: 0.0, accountNumber: accountNumber,
                   customer: customer)
    }

    Account deposit(amount) {
        cloneWithChange(balance: balance + amount)
    }

    Account withdraw(amount) {
        cloneWithChange(balance: balance - amount)
    }

    private Account cloneWithChange(changes) {
        def newProps = ...
        new Account(newProps)
    }
}
@Immutable
class Account...
    BigDecimal balance
    long accountNumber
    String customer
    long version

   static Account create(long accountNumber, String customer) {
       new Account(balance: 0.0, accountNumber: accountNumber,
                   customer: customer, version: 0)
   }

   Account incrementVersion() {
       cloneWithChange(version: version + 1)
   }

   Account deposit(amount) {
       cloneWithChange(balance: balance + amount)
   }

   Account withdraw(amount) {
       cloneWithChange(balance: balance - amount)
   }
class Bank...
    private final accounts = new ConcurrentHashMap()
   Account createNewAccountFor(String customer) {
       Account account =
         Account.create(nextAccountNumber(), customer).incrementVersion();
       accounts[account.getAccountNumber()] = account
   }
   Account accountByNumber(long accountNumber) {
       accounts[accountNumber]
   }
   synchronized boolean update(Account... accountsToUpdate) {
       if (accountsToUpdate.any {acc ->
           acc.version != accounts[acc.accountNumber]?.version
       }) {
            return false
       }
       accountsToUpdate.each {acc ->
           accounts[it.accountNumber] = it.incrementVersion()
       }
       return true
   }
@Immutable
class MoneyTransfer {

    BigDecimal amount
    long from, to

    boolean execute(Bank bank) {
        while(true) {
            Account source = bank.accountByNumber(from);
            Account target = bank.accountByNumber(to);
            if (source.balance < amount)
                return false
            source = source.withdraw(amount)
            target = target.deposit(amount)
            if (bank.update(source, target))
                return true
        }
    }
}
Immutability im Großen

• Performante Implementierung von
  "Immutable Data Types" ist möglich
• In funktionale Programmiersprachen
  sind unveränderliche Werte die Norm
  und veränderlicher State die Ausnahme
 ‣ Beispiel Clojure: Fokus auf unveränderliche
    Werte und explizite Mechanismen zur
    Manipulation des aktuellen Werts einer
    Entity
Message Passing - Actors

• Vollständiger Verzicht auf "shared
  state"
• Aktoren empfangen und verschicken
  Nachrichten
  ‣ Asynchron und nicht-blockierend
  ‣ Jeder Actor hat seine "Mailbox"
• Immer nur ein aktiver Thread pro Aktor
class BankActor extends AbstractPooledActor...
   void act() {
       loop {
           react { message ->
                switch(message) {
                    case CreateAccount:
                         createNewAccountFor(message.customer); break
                    case GetAccount:
                         accountByNumber(message.accountNumber); break
                    case GetAllAccountNumbers:
                         allAccountNumbers(); break
                    case UpdateAccounts:
                         update(message.accounts); break
                    default:
                      println "${this.class}: I don't understand $message"
   }   }   }   }


@Immutable class CreateAccount { String customer }
@Immutable class UpdateAccounts { Account[] accounts }
@Immutable class GetAccount { long accountNumber }
@Immutable class GetAllAccountNumbers {}
class BankActor extends AbstractPooledActor...
    private accounts = [:]
    private lastAccountNumber = 0
   void createNewAccountFor(String customer) {
       Account account = ...
       reply account
   }
   void accountByNumber(long accountNumber) {
       reply accounts[accountNumber]
   }
   void allAccountNumbers() {
       reply accounts.keySet() as List
   }
   void update(Account[] accountsToUpdate) {
       if (accountsToUpdate.any { acc ->
           acc.version != accounts[acc.accountNumber]?.version
       }) {
           reply false
       } else {
           accountsToUpdate.each {acc ->
               accounts[acc.accountNumber] = acc.incrementVersion()
           }
           reply true
   }   }
class MoneyTransferActor...
 BankActor bank
 void act() {
   ...
     switch(message) {
         case TransferMoney: transferMoney(message); break;
 }
 void transferMoney(transfer) {
   while(true) {
     Account source = bank.sendAndWait(new GetAccount(transfer.from));
     Account target = bank.sendAndWait(new GetAccount(transfer.to));
     if (source.balance < transfer.amount) {
       reply new TransferResult(transfer.transferId, false)
       break
     }
     source = source.withdraw(transfer.amount)
     target = target.deposit(transfer.amount)
     if (bank.sendAndWait(new UpdateAccounts(accounts: [source, target]))) {
       reply new TransferResult(transfer.transferId, true)
       break
 } } }

@Immutable class TransferMoney {
    long transferId
    BigDecimal amount
    long from, to
}
Actors - Vorteile

• Unabhängige Aktoren
• Skalierbar
• Verteilbar
• Leichtere Vermeidung von
 ‣ Race Conditions
 ‣ Dead Locks
 ‣ Starvation
Fehler-tolerant &
      hoch-verfügbar

• Mehrere redundante Aktoren stehen für
  die gleiche Aufgabe zur Verfügung
• Organisation in Supervisor-Hierarchien
  ‣ Kein lokales Exception-Handling, sondern
    Neustart eines Aktors im Fehlerfalle
Actors - Nachteile


• Nicht geeignet für Probleme, die einen
  echten Konsens über gemeinsame
  Objekte erfordern
• Kommunikation über asynchrone
  Nachrichten ist für viele
  Problemstellungen eine Komplizierung
Parallele Collections

• Wir arbeiten auf allen Elementen einer
  Collection gleichzeitig
• Voraussetzung: Die Einzeloperationen
  sind unabhängig voneinander
• Typische parallele Aktionen:
  ‣ Filtern
  ‣ Transformieren
Beispiel:
             Standardabweichung aller
               Millionärskonten von
                     1.000.000
                   transform                    filter                        transform
Kontonummer                          Saldo                 Mio-Saldo                              Diff2Mio
                   transform             filter                 transform
  Kontonummer        transform Saldo       filter Mio-Saldo       transform Diff2Mio
    Kontonummer        transform Saldo       filter Mio-Saldo       transform Diff2Mio
      Kontonummer        transform Saldo       filter Mio-Saldo       transform Diff2Mio
                           transform Saldo       filter Mio-Saldo       transform Diff2Mio




                                                                                                              sum
        Kontonummer
          Kontonummer        transform Saldo       filter Mio-Saldo       transform Diff2Mio
            Kontonummer        transform Saldo       filter Mio-Saldo       transform Diff2Mio
              Kontonummer        transform Saldo       filter Mio-Saldo       transform Diff2Mio




                                                                                                                 mier
              Kontonummer                   Saldo           Mio-Saldo                  Diff2Mio
                                                                                             su
                                                                                                  m




                                                                                                                 en
                                                                                                   m
                                                                                                      ier
                                                                                                         en

                                                                                                         Std-Dev
gpars' parallele Collections

Parallelizer.doParallel(numThreads) {
	    def allBalances = bank.allAccountNumbers().collectParallel {
	    	   return bank.accountByNumber(it).balance
	    }
	    def millionaireBalances = allBalances.findAllParallel { it >= MILLION }
	    def deviations = millionaireBalances.collectParallel { balance ->
	    	   def diffToMean = balance - MILLION
	    	   return (diffToMean * diffToMean)
	    }
	    def count = deviations.size()
	    def average = Math.sqrt(deviations.sumParallel() / count)
}
Collection
..work..

             ..work..

             ..work..
Collection              Collection
             ..work..

             ..work..
..work..                ..work..

             ..work..                ..work..

             ..work..                ..work..
Collection              Collection
             ..work..                ..work..

             ..work..                ..work..
MapReduce
• Von Google patentierter Ansatz zur
  verteilten Bearbeitung großer
  Datencluster
• Name stammt von den map- und reduce-
  Funktionen funktionaler Sprachen
• Idee: Einzelne Datensätze werden durch
  eine Pipeline von Arbeitsschritten
  transformiert (map) und am Ende zu
  einem Ergebnis zusammengeführt
  (reduce)
Data Set



Data Set



Data Set



Data Set



Data Set




Data Set
Data Set   map   Data Set



Data Set   map   Data Set



Data Set   map   Data Set



Data Set   map   Data Set



Data Set   map   Data Set




Data Set   map   Data Set
Data Set   map   Data Set

                            reduce

Data Set   map   Data Set



Data Set   map   Data Set            reduce

                            reduce

Data Set   map   Data Set
                                              reduce



Data Set   map   Data Set
                                                   Result
                            reduce

Data Set   map   Data Set
Data Set   map   Data Set

                            reduce

Data Set   map   Data Set



Data Set   map   Data Set            reduce

                            reduce

Data Set   map   Data Set
                                              reduce



Data Set   map   Data Set
                                                   Result
                            reduce

Data Set   map   Data Set
Map-Reduce mit gpars

Parallelizer.doParallel(numThreads) {
	   def deviations = bank.allAccountNumbers().parallel.map {accountNumber ->
	   	   bank.accountByNumber(accountNumber).balance
	   }.filter { balance -> balance >= MILLION }.map {balance ->
	   	   def diffToMean = balance - MILLION
	   	   (diffToMean * diffToMean)
	   }
	   //def sum = deviations.sum()
	   def sum = deviations.reduce {a,b -> a + b}
	   def count = deviations.size()
	   def average = Math.sqrt(sum / count)
}
Kritik an MapReduce

• Patentierbarkeit (claim of novelty)
  wird angezweifelt
• Wie breit ist der Problemraum, für den
  MapReduce sinnvoll angewandt werden
  kann?
• Wie sinnvoll ist MapReduce im nicht
  verteilten Kontext?
Performance-Vergleich
• Hardware:
  ‣ Intel i7: 4 cores, 8 hyper threads
• Wettbewerber
  ‣ Sequenziell
  ‣ Parallel Collections
  ‣ MapReduce
• Vefahren
  ‣ jeweils 1 - 8 Threads
  ‣ jeweils 10 Samples
  ‣ 1000, 2000, 10.000, 50.000, 100.000 Konten
Erwartete Beschleunigung
                        3.00


                        2.50
Beschleunigungsfaktor




                        2.00


                        1.50


                        1.00


                        0.50


                          0
                               1   2            3       4          5        6          7    8
                                                    Anzahl der Threads

                                       Sequential    Parallel Collections       MapReduce
Beschleunigung bei 1000 Konten
                        2.00




                        1.50
Beschleunigungsfaktor




                        1.00




                        0.50




                          0
                               1   2            3       4          5        6          7    8
                                                    Anzahl der Threads

                                       Sequential    Parallel Collections       MapReduce
Beschleunigung bei 2000 Konten
                        2.00




                        1.50
Beschleunigungsfaktor




                        1.00




                        0.50




                          0
                               1   2            3       4          5        6          7    8
                                                    Anzahl der Threads

                                       Sequential    Parallel Collections       MapReduce
Beschleunigung bei 10000 Konten
                        2.00




                        1.50
Beschleunigungsfaktor




                        1.00




                        0.50




                          0
                               1   2            3       4          5        6          7    8
                                                    Anzahl der Threads

                                       Sequential    Parallel Collections       MapReduce
Beschleunigung bei 50000 Konten
                        2.00




                        1.50
Beschleunigungsfaktor




                        1.00




                        0.50




                          0
                               1   2            3       4          5        6          7    8
                                                    Anzahl der Threads

                                       Sequential    Parallel Collections       MapReduce
Beschleunigung bei 100000 Konten
                        2.00




                        1.50
Beschleunigungsfaktor




                        1.00




                        0.50




                          0
                               1   2            3       4          5        6          7    8
                                                    Anzahl der Threads

                                       Sequential    Parallel Collections       MapReduce
Data Flows


• Beschreibung der Abhängigkeiten von
  Daten untereinander
• Grundkonzept: Dataflow-Variablen
 ‣ Wert kann nur einmal gebunden werden
 ‣ Abfrage des Wertes blockiert bis er
    gebunden wurde
gpars - DataFlows
def x = new DataFlowVariable()
def y = new DataFlowVariable()
def z = new DataFlowVariable()
task { z << x.val + y.val }
task { x << 40 }
task { y << 2 }
assert 42 == z.val
gpars - DataFlows
def x = new DataFlowVariable()
def y = new DataFlowVariable()
def z = new DataFlowVariable()
task { z << x.val + y.val }
task { x << 40 }
task { y << 2 }
assert 42 == z.val



def df = new DataFlows()
task { df.z = df.x + df.y }
task { df.x = 40 }
task { df.y = 2 }
assert 42 == df.z
Vorteile von Dataflows

• Kein Locking notwendig
• Keine Race-Conditions
• Deterministisch
 ‣ Deadlocks sind möglich, sie passieren dann
    aber immer!
• Kein Unterschied zwischen
  sequenziellem und nebenläufigem Code
Beispiel: Depotwert
def depot = ['AAPL':10, 'GOOG':2, 'IBM':20, 'ORCL':500]
def stocks = depot.keySet()
def prices = new DataFlows()
def value = new DataFlowVariable()	
                                  	
stocks.each {stock ->
	   task {
	   	    prices[stock] = getStockPrice(stock)
	   }
}
task {
	   value << stocks.collect { stock ->
	   	   prices[stock] * depot[stock]
	   }.sum()
}	 	
println value.val
Mehr über Data Flows

• Einschränkungen
  ‣ Nicht für jede Problemstellung geeignet
  ‣ Berechnungen dürfen keine Seiteneffekte
    haben
• Erweiterungen
  ‣ Data flow streams, Data flow operators
• JVM-Implementierungen
  ‣ Scala Dataflow, FlowJava (akademisch & tot)
Nicht behandelt


• Fork/Join (jsr166y)
• CSP:
  Communicating Sequential Processes
• Declarative Concurrency
• und andere...
Fazit

• Multi-thread-orientierte und Lock-
  basierte Programmierung ist schwierig
  ‣ Hauptgrund: Thread-sichere Objekte sind
    zusammengesetzt nicht komponierbar
• "Immutability" erleichtert das parallele
  Leben sehr
• Andere Paradigmen können helfen, der
  heilige Kral der Multi-Core-Entwicklung
  ist aber (noch) nicht gefunden
Links
Zum Lesen
   http://www.infoq.com/presentations/
   goetz-concurrency-past-present
   http://www.slideshare.net/jboner/
   state-youre-doing-it-wrong-javaone-2009
   http://ajaxonomy.com/2008/news/
   toward-better-concurrency
   http://igoro.com/archive/
   gallery-of-processor-cache-effects/
Zum Ausprobieren
   http://gpars.codehaus.org/
   http://clojure.org/
   http://github.com/jboner/scala-dataflow

Weitere ähnliche Inhalte

Was ist angesagt?

Multithreading in c# mit tpl
Multithreading in c# mit tplMultithreading in c# mit tpl
Multithreading in c# mit tplDavidT27
 
Was ist neu in Java 6, 7, 8, ...
Was ist neu in Java 6, 7, 8, ...Was ist neu in Java 6, 7, 8, ...
Was ist neu in Java 6, 7, 8, ...Andreas Schreiber
 
Einführung in die funktionale Programmierung mit Clojure
Einführung in die funktionale Programmierung mit ClojureEinführung in die funktionale Programmierung mit Clojure
Einführung in die funktionale Programmierung mit ClojureSascha Koch
 
Übungsaufgaben SS2010
Übungsaufgaben SS2010Übungsaufgaben SS2010
Übungsaufgaben SS2010maikinger
 

Was ist angesagt? (7)

Multithreading in c# mit tpl
Multithreading in c# mit tplMultithreading in c# mit tpl
Multithreading in c# mit tpl
 
Was ist neu in Java 6, 7, 8, ...
Was ist neu in Java 6, 7, 8, ...Was ist neu in Java 6, 7, 8, ...
Was ist neu in Java 6, 7, 8, ...
 
JavaScript Performance
JavaScript PerformanceJavaScript Performance
JavaScript Performance
 
Do while
Do whileDo while
Do while
 
Einführung in die funktionale Programmierung mit Clojure
Einführung in die funktionale Programmierung mit ClojureEinführung in die funktionale Programmierung mit Clojure
Einführung in die funktionale Programmierung mit Clojure
 
Übungsaufgaben SS2010
Übungsaufgaben SS2010Übungsaufgaben SS2010
Übungsaufgaben SS2010
 
Basisinformationstechnologie I WiSem 2015 / 2016 | 07_Rechnertechnologie III:...
Basisinformationstechnologie I WiSem 2015 / 2016 | 07_Rechnertechnologie III:...Basisinformationstechnologie I WiSem 2015 / 2016 | 07_Rechnertechnologie III:...
Basisinformationstechnologie I WiSem 2015 / 2016 | 07_Rechnertechnologie III:...
 

Ähnlich wie Mein paralleles Leben als Java-Entwickler

Skalierbare Anwendungen mit Google Go
Skalierbare Anwendungen mit Google GoSkalierbare Anwendungen mit Google Go
Skalierbare Anwendungen mit Google GoFrank Müller
 
Go - Googles Sprache für skalierbare Systeme
Go - Googles Sprache für skalierbare SystemeGo - Googles Sprache für skalierbare Systeme
Go - Googles Sprache für skalierbare SystemeFrank Müller
 
Schneller Einstieg in OpenCL mit C++ Bindings
Schneller Einstieg in OpenCL mit C++ BindingsSchneller Einstieg in OpenCL mit C++ Bindings
Schneller Einstieg in OpenCL mit C++ BindingsPatrick Charrier
 
2006 - NRW Conf: Asynchronous asp.net
2006 - NRW Conf: Asynchronous asp.net2006 - NRW Conf: Asynchronous asp.net
2006 - NRW Conf: Asynchronous asp.netDaniel Fisher
 
Einführung in die funktionale Programmierung
Einführung in die funktionale ProgrammierungEinführung in die funktionale Programmierung
Einführung in die funktionale ProgrammierungDigicomp Academy AG
 
Wieso Informatiker bei der Informationssicherheit scheitern
Wieso Informatiker bei der Informationssicherheit scheiternWieso Informatiker bei der Informationssicherheit scheitern
Wieso Informatiker bei der Informationssicherheit scheiternDigicomp Academy AG
 
Lösungsorientierte Fehlerbehandlung
Lösungsorientierte FehlerbehandlungLösungsorientierte Fehlerbehandlung
Lösungsorientierte Fehlerbehandlungroskakori
 
Python Mike Müller
Python Mike MüllerPython Mike Müller
Python Mike MüllerAberla
 
Praesentation TYPO3Camp Berlin Speed mit Extbase
Praesentation TYPO3Camp Berlin Speed mit ExtbasePraesentation TYPO3Camp Berlin Speed mit Extbase
Praesentation TYPO3Camp Berlin Speed mit ExtbaseStefan Frömken
 
Lightweight AOP with CDI and JPA
Lightweight AOP with CDI and JPALightweight AOP with CDI and JPA
Lightweight AOP with CDI and JPAmh0708
 
SEROM 2018 - 11/14/17/20 - C++ gestern heute und morgen
SEROM 2018 - 11/14/17/20 - C++ gestern heute und morgenSEROM 2018 - 11/14/17/20 - C++ gestern heute und morgen
SEROM 2018 - 11/14/17/20 - C++ gestern heute und morgenDr. Herwig Henseler
 
Fr os con2010_devel_nytprof
Fr os con2010_devel_nytprofFr os con2010_devel_nytprof
Fr os con2010_devel_nytprofRenee Baecker
 

Ähnlich wie Mein paralleles Leben als Java-Entwickler (20)

Skalierbare Anwendungen mit Google Go
Skalierbare Anwendungen mit Google GoSkalierbare Anwendungen mit Google Go
Skalierbare Anwendungen mit Google Go
 
BIT I WiSe 2014 | Basisinformationstechnologie I - 08: Programmiersprachen I
BIT I WiSe 2014 | Basisinformationstechnologie I - 08: Programmiersprachen IBIT I WiSe 2014 | Basisinformationstechnologie I - 08: Programmiersprachen I
BIT I WiSe 2014 | Basisinformationstechnologie I - 08: Programmiersprachen I
 
Go - Googles Sprache für skalierbare Systeme
Go - Googles Sprache für skalierbare SystemeGo - Googles Sprache für skalierbare Systeme
Go - Googles Sprache für skalierbare Systeme
 
Schneller Einstieg in OpenCL mit C++ Bindings
Schneller Einstieg in OpenCL mit C++ BindingsSchneller Einstieg in OpenCL mit C++ Bindings
Schneller Einstieg in OpenCL mit C++ Bindings
 
OpenCL Grundlagen
OpenCL GrundlagenOpenCL Grundlagen
OpenCL Grundlagen
 
Microbenchmarks - Wer nicht weiß, was er misst misst Mist
Microbenchmarks - Wer nicht weiß, was er misst misst MistMicrobenchmarks - Wer nicht weiß, was er misst misst Mist
Microbenchmarks - Wer nicht weiß, was er misst misst Mist
 
2006 - NRW Conf: Asynchronous asp.net
2006 - NRW Conf: Asynchronous asp.net2006 - NRW Conf: Asynchronous asp.net
2006 - NRW Conf: Asynchronous asp.net
 
Einführung in die funktionale Programmierung
Einführung in die funktionale ProgrammierungEinführung in die funktionale Programmierung
Einführung in die funktionale Programmierung
 
Wieso Informatiker bei der Informationssicherheit scheitern
Wieso Informatiker bei der Informationssicherheit scheiternWieso Informatiker bei der Informationssicherheit scheitern
Wieso Informatiker bei der Informationssicherheit scheitern
 
Lösungsorientierte Fehlerbehandlung
Lösungsorientierte FehlerbehandlungLösungsorientierte Fehlerbehandlung
Lösungsorientierte Fehlerbehandlung
 
Node.js Security
Node.js SecurityNode.js Security
Node.js Security
 
Python Mike Müller
Python Mike MüllerPython Mike Müller
Python Mike Müller
 
C++11 und c++14
C++11 und c++14C++11 und c++14
C++11 und c++14
 
Akka.NET Teil 1 - Verteilte Architektur von Beginn an
Akka.NET Teil 1 - Verteilte Architektur von Beginn anAkka.NET Teil 1 - Verteilte Architektur von Beginn an
Akka.NET Teil 1 - Verteilte Architektur von Beginn an
 
Praesentation TYPO3Camp Berlin Speed mit Extbase
Praesentation TYPO3Camp Berlin Speed mit ExtbasePraesentation TYPO3Camp Berlin Speed mit Extbase
Praesentation TYPO3Camp Berlin Speed mit Extbase
 
Lightweight AOP with CDI and JPA
Lightweight AOP with CDI and JPALightweight AOP with CDI and JPA
Lightweight AOP with CDI and JPA
 
TypeScript
TypeScriptTypeScript
TypeScript
 
Tutorium 5
Tutorium 5Tutorium 5
Tutorium 5
 
SEROM 2018 - 11/14/17/20 - C++ gestern heute und morgen
SEROM 2018 - 11/14/17/20 - C++ gestern heute und morgenSEROM 2018 - 11/14/17/20 - C++ gestern heute und morgen
SEROM 2018 - 11/14/17/20 - C++ gestern heute und morgen
 
Fr os con2010_devel_nytprof
Fr os con2010_devel_nytprofFr os con2010_devel_nytprof
Fr os con2010_devel_nytprof
 

Mehr von jlink

Java Script Ist Anders
Java Script Ist AndersJava Script Ist Anders
Java Script Ist Andersjlink
 
Agile08: Test Driven Ajax
Agile08: Test Driven AjaxAgile08: Test Driven Ajax
Agile08: Test Driven Ajaxjlink
 
Behaviour-Driven Development
Behaviour-Driven DevelopmentBehaviour-Driven Development
Behaviour-Driven Developmentjlink
 
Mehr Dynamik Durch Skriptsprachen
Mehr Dynamik Durch SkriptsprachenMehr Dynamik Durch Skriptsprachen
Mehr Dynamik Durch Skriptsprachenjlink
 
Mehr Dynamik Mit Groovy
Mehr Dynamik Mit GroovyMehr Dynamik Mit Groovy
Mehr Dynamik Mit Groovyjlink
 
Von Java Zu Groovy
Von Java Zu GroovyVon Java Zu Groovy
Von Java Zu Groovyjlink
 
Automated Web 2.0 Testing
Automated Web 2.0 TestingAutomated Web 2.0 Testing
Automated Web 2.0 Testingjlink
 
AdvancedTdd
AdvancedTddAdvancedTdd
AdvancedTddjlink
 
XP Day Germany 2006 - Keynote
XP Day Germany 2006 - KeynoteXP Day Germany 2006 - Keynote
XP Day Germany 2006 - Keynotejlink
 
Testgetriebene Softwareentwicklung
Testgetriebene SoftwareentwicklungTestgetriebene Softwareentwicklung
Testgetriebene Softwareentwicklungjlink
 

Mehr von jlink (10)

Java Script Ist Anders
Java Script Ist AndersJava Script Ist Anders
Java Script Ist Anders
 
Agile08: Test Driven Ajax
Agile08: Test Driven AjaxAgile08: Test Driven Ajax
Agile08: Test Driven Ajax
 
Behaviour-Driven Development
Behaviour-Driven DevelopmentBehaviour-Driven Development
Behaviour-Driven Development
 
Mehr Dynamik Durch Skriptsprachen
Mehr Dynamik Durch SkriptsprachenMehr Dynamik Durch Skriptsprachen
Mehr Dynamik Durch Skriptsprachen
 
Mehr Dynamik Mit Groovy
Mehr Dynamik Mit GroovyMehr Dynamik Mit Groovy
Mehr Dynamik Mit Groovy
 
Von Java Zu Groovy
Von Java Zu GroovyVon Java Zu Groovy
Von Java Zu Groovy
 
Automated Web 2.0 Testing
Automated Web 2.0 TestingAutomated Web 2.0 Testing
Automated Web 2.0 Testing
 
AdvancedTdd
AdvancedTddAdvancedTdd
AdvancedTdd
 
XP Day Germany 2006 - Keynote
XP Day Germany 2006 - KeynoteXP Day Germany 2006 - Keynote
XP Day Germany 2006 - Keynote
 
Testgetriebene Softwareentwicklung
Testgetriebene SoftwareentwicklungTestgetriebene Softwareentwicklung
Testgetriebene Softwareentwicklung
 

Mein paralleles Leben als Java-Entwickler

  • 1. Mein paralleles Leben als Java-Programmierer
  • 3. Stephan Mosel - http://www.flickr.com/photos/moe/9474926/
  • 5.
  • 6. public class MyClass { private volatile String myProp = ""; public synchronized String getMyProp() { return myProp; } public void setMyProp(String value) { synchronized (this) { myProp = value; this.notifyAll(); } } public synchronized void waitUntilPropNotEmpty() throws InterruptedException { while (myProp.equals("")) { this.wait(); } } }
  • 7. public class MyClassTest... private volatile boolean succeeded = false; @Test public void waitingForAValue() throws Exception { final MyClass my = new MyClass(); Thread myThread = new Thread(new Runnable() { public void run() { try { my.waitUntilPropNotEmpty(); succeeded = true; } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); myThread.start(); my.setMyProp(""); assertFalse(succeeded); my.setMyProp("test"); myThread.join(); assertTrue(succeeded); }
  • 9. Angenehme Position des Halbwissens "Nebenläufigkeit wird völlig überbewertet"
  • 10. Angenehme Position des Halbwissens "Nebenläufigkeit wird völlig überbewertet" "Das erledigen die Frameworks für mich"
  • 11. Angenehme Position des Halbwissens "Nebenläufigkeit wird völlig überbewertet" "Das erledigen die Frameworks für mich" "Ich trenne meine Fachlogik von Nebenläufigkeit und Threading"
  • 12. Angenehme Position des Halbwissens "Nebenläufigkeit wird völlig überbewertet" "Das erledigen die Frameworks für mich" "Ich trenne meine Fachlogik von Nebenläufigkeit und Threading" "Ich denke mal ganz intensiv darüber nach und dann funktioniert das"
  • 14. aus Herb Sutter: "The free lunch is over"
  • 15. "Applications will increasingly need to be concurrent if they want to fully exploit continuing exponential CPU throughput gains." Herb Sutter
  • 16.
  • 17.
  • 18.
  • 19. Was ist das Problem?
  • 20. Was ist das Problem? • Safety: Nothing bad ever happens
  • 21. Was ist das Problem? • Safety: Nothing bad ever happens • Liveness: Something good eventually happens
  • 22. Was ist das Problem? • Safety: Nothing bad ever happens • Liveness: Something good eventually happens • Performance: Things happen faster
  • 23. Safety - Alles ist korrekt public class Counter { private long count = 0; public long value() { return count; } public void inc() { count++; } } Counter counter = new Counter(); //in Thread A: counter.inc(); //in Thread B: counter.inc(); //danach: assert counter.value() == 2;
  • 24. Safety - Alles ist korrekt public class Counter { private long count = 0; public long value() { counter.inc(): return count; } public void inc() { temp = counter.count count++; } } temp = temp + 1 Counter counter = new Counter(); //in Thread A: counter.count = temp counter.inc(); //in Thread B: counter.inc(); //danach: assert counter.value() == 2;
  • 25. Safety - Alles ist korrekt public class Counter { private long count = 0; public long value() { return count; } public void inc() { count++; } } Counter counter = new Counter(); //in Thread A: counter.inc(); //in Thread B: counter.inc(); //danach: assert counter.value() == 2;
  • 26. Safety - Alles ist korrekt public class Counter { Thread A: Thread B: private long count = 0; public long value() { temp = 0 return count; } public void inc() { temp = 0 + 1 count++; } } count = 1 Counter counter = new Counter(); temp = 1 //in Thread A: counter.inc(); temp = 1 + 1 //in Thread B: counter.inc(); //danach: count = 2 assert counter.value() == 2;
  • 27. Safety - Alles ist korrekt public class Counter { private long count = 0; public long value() { return count; } public void inc() { count++; } } Counter counter = new Counter(); //in Thread A: counter.inc(); //in Thread B: counter.inc(); //danach: assert counter.value() == 2;
  • 28. Safety - Alles ist korrekt public class Counter { Thread A: Thread B: private long count = 0; public long value() { temp = 0 return count; } public void inc() { temp = 0 + 1 count++; } } temp = 0 Counter counter = new Counter(); count = 1 //in Thread A: counter.inc(); temp = 0 + 1 //in Thread B: counter.inc(); //danach: count = 1 assert counter.value() == 2;
  • 29.
  • 30. Thread A: Thread B: Step A.1 Step B.1 Step A.2 Step B.2 Step A.3 Step B.3 Step A.4 Step B.4 Step A.5
  • 31.
  • 32. Thread A: Thread B: Step A.1 Step B.1 Step A.3 Step B.3 Step A.2 Step B.2 Step A.5 Step B.4 Step A.4
  • 33. Was bedeutet korrekt in einem nebenläufigen Programm?
  • 34. Synchronisation nebenläufiger Objekte Bestimmte Programmbereiche werden durch Schlösser (Lock) in eine definierte sequenzielle Reihenfolge gebracht
  • 35. Locks public class LockedCounter { Thread A: Thread B: private long count = 0; private Lock lock lock.lock() = new ReentrantLock(); public long value() { lock.lock(); temp = 0 lock.lock() long value = count; lock.unlock(); muss warten return value; } lock.unlock() public void inc() { lock.lock(); temp = 1 count++; lock.unlock(); } } count = 2 lock.unlock()
  • 36. Locks sind nicht umsonst
  • 37. Locks sind nicht umsonst • Sie bergen das Risiko von Deadlocks
  • 38. Locks sind nicht umsonst • Sie bergen das Risiko von Deadlocks • Sie führen zu sequenzialisierter Programmausführung
  • 39. Locks sind nicht umsonst • Sie bergen das Risiko von Deadlocks • Sie führen zu sequenzialisierter Programmausführung • Sie benötigen zusätzliche Prozessorzyklen ‣ Basieren auf primitiven Operationen (TAS oder CAS), welche (oft) auf "richtiges" Memory zugreifen müssen
  • 40. Caching, Memory & Co Prozessor Prozessor Core Core L1-Cache L1-Cache L2-Cache Memory
  • 41. Caching, Memory & Co Prozessor Prozessor Core Core 1-2 L1-Cache L1-Cache L2-Cache Memory
  • 42. Caching, Memory & Co Prozessor Prozessor Core Core 20-30 1-2 L1-Cache L1-Cache L2-Cache Memory
  • 43. Caching, Memory & Co Prozessor Prozessor Core Core 20-30 1-2 L1-Cache L1-Cache L2-Cache 300-400 Zyklen Memory
  • 44.
  • 45. Wie machen wir ein Objekt für andere Threads sichtbar?
  • 46. Prozessor Core Core L1-Cache L1-Cache L2-Cache Memory
  • 47. Prozessor Unshared Core Core Object L1-Cache L1-Cache L2-Cache Memory
  • 48. Prozessor Unshared Core Core Object L1-Cache L1-Cache L2-Cache Shared Object Memory
  • 49. Sicheres Publizieren - Safe Publication • Initialisierung in statischem Initializer • Speichern in volatile-Feld oder in AtomicReference • Ein unveränderliches Objekt • Speichern in einem Feld, das vollständig durch einen Lock abgesichert ist
  • 50. Grundsätze für die Verwendung von Locks Halte genau dann einen Lock,
  • 51. Grundsätze für die Verwendung von Locks Halte genau dann einen Lock, • wenn du auf gemeinsamen und veränder- lichen Zustand zugreifst
  • 52. Grundsätze für die Verwendung von Locks Halte genau dann einen Lock, • wenn du auf gemeinsamen und veränder- lichen Zustand zugreifst • wenn du atomare Operationen ausführst ‣ check then act ‣ read-modify-write
  • 53. Grundsätze für die Verwendung von Locks Halte genau dann einen Lock, • wenn du auf gemeinsamen und veränder- lichen Zustand zugreifst • wenn du atomare Operationen ausführst ‣ check then act ‣ read-modify-write Halte den Lock nicht länger als nötig
  • 54. Liveness - Jeder sollte an die Reihe kommen • Gefahr 1: Deadlocks • Gefahr 2: Starvation
  • 55. Deadlock public class SimpleDeadlock... private final Object left Thread A: Thread B: = new Object(); private final Object right lock left = new Object(); public void leftRight() { lock right synchronized (left) { synchronized (right) { try to doSomething(); lock right } try to } lock left } public void rightLeft() { synchronized (right) { synchronized (left) { wait for ever doSomething(); } wait for ever } }
  • 56. Starvation Bei hohem "Wettbewerb" (contention) um die gleiche Ressource (z.B. Lock) kommen nicht alle wartenden Threads an die Reihe
  • 57. Performance • Mehr Prozessoren/Kerne sollten zu besserer Performance führen ‣ Besseres Antwortverhalten durch Verlagerung lang-laufender Berechnungen in parallele Threads ‣ Höherer Durchsatz durch Aufteilung von Berechnungen in mehrere Threads
  • 59. Amdahl's Law F: nicht parallelisierbare Anteil eines Programms N: Anzahl der Kerne / Prozessoren
  • 60. F = 0,2 5.00 3.75 Beschleunigung 2.50 1.25 0 1 2 3 4 5 10 20 100 Anzahl der Kerne / Prozessoren
  • 61. Wie parallelisiert man ein Programm?
  • 62. Wie parallelisiert man ein Programm? • Parallelisierung des Kontrollflusses
  • 63. Wie parallelisiert man ein Programm? • Parallelisierung des Kontrollflusses • Parallellisierung der Daten
  • 64. Parallelisierung des Kontrollflusses Step 1 Step 2 Step 3 Step 4 Step 5 Step 6
  • 65. Parallelisierung des Kontrollflusses Step 1 Step 1 Step 2 Step 2 Step 3 Step 3 Step 4a Step 4b Step 4c Step 4 Step 5 Step 6 Step 5 Step 6
  • 66. Rekursive Zerlegung des Kontrollflusses (Fork/Join) Step 1 Step 2 Step 3 Step 4 Step 5 Step 6
  • 67. Rekursive Zerlegung des Kontrollflusses (Fork/Join) Step 1 Step 1 Step 2 Steps 2-5 Step 3 2-5.1 2-5.2 2-5.n Step 4 2-5.1.1 2-5.1.2 2-5.1.n Step 5 Step 6 Step 6
  • 68. Parallelisierung der Daten for 1..n do Step 1 Step 2 Step 3 Step 4 Step 5
  • 69. Parallelisierung der Daten for 1..n do for each element do in parallel: Step 1 Step 1 Step 1 Step 1 Step 1 Step 1 Step 2 Step 2 Step 2 Step 2 Step 2 Step 2 Step 3 Step 3 Step 3 Step 3 Step 3 Step 3 Step 4 Step 4 Step 4 Step 4 Step 4 Step 4 Step 5 Step 5 Step 5 Step 5 Step 5 Step 5
  • 70. java.util.concurrent[.a tomic|locks]
  • 71. Locks und Conditions <<Interface>> <<Interface>> Lock Condition creates lock() await() lockInterruptibly() await(timeout) tryLock(timeout) signal() unlock() signalAll() ReentrantLock
  • 72. public class MyNewClass... private String myProp = ""; private Lock lock = new ReentrantLock(); private Condition propNotEmpty = lock.newCondition(); public String getMyProp() { lock.lock(); try { return myProp; } finally {lock.unlock();} } public void setMyProp(String value) { lock.lock(); try { myProp = value; propNotEmpty.signalAll(); } finally {lock.unlock();} } public void waitUntilPropNotEmpty() throws InterruptedException { lock.lock(); try { while (myProp.equals("")) { propNotEmpty.await(); } } finally {lock.unlock();} }
  • 73. public class MyNewClass... private String myProp = ""; private Lock lock = new ReentrantLock(); private Condition propNotEmpty = lock.newCondition(); public String getMyProp() { lock.lock(); try { return myProp; } finally {lock.unlock();} } public void setMyProp(String value) { lock.lock(); try { myProp = value; propNotEmpty.signalAll(); } finally {lock.unlock();} } public void waitUntilPropNotEmpty() throws InterruptedException { lock.lock(); try { while (myProp.equals("")) { propNotEmpty.await(); } } finally {lock.unlock();} }
  • 74. public class MyNewClass... private String myProp = ""; private Lock lock = new ReentrantLock(); private Condition propNotEmpty = lock.newCondition(); public String getMyProp() { lock.lock(); try { return myProp; } finally {lock.unlock();} } public void setMyProp(String value) { lock.lock(); try { myProp = value; propNotEmpty.signalAll(); } finally {lock.unlock();} } public void waitUntilPropNotEmpty() throws InterruptedException { lock.lock(); try { while (myProp.equals("")) { propNotEmpty.await(); } } finally {lock.unlock();} }
  • 75. Implizite oder explizite Locks / Conditions • Verwende implizite Locks und Conditions, wenn dir deren Feature-Set genügt ‣ Performance seit Java 6 praktisch gleich • Verwende explizite Locks und Conditions, wenn du mehr brauchst ‣ Unterbrechbarkeit ‣ tryLock() ‣ Timeouts ‣ Mehr als eine Condition-Queue
  • 76. Das Executor-Framework Nie wieder new Thread(...).start() ! <<Interface>> Executor <<Interface>> execute(Runnable) Callable<T> call(): T <<Interface>> ExecutorService <<Interface>> submit(Runnable):Future<?> Future<T> submit(Callable<T>):Future<T> shutdown() cancel(mayInterrupt) awaitTermination(timeout) get(): T get(timeout): T
  • 77. public class MyNewClassTest... private volatile boolean succeeded = false; @Test public void waitingForAValue() throws Exception { final MyNewClass my = new MyNewClass(); ExecutorService executor = Executors.newCachedThreadPool(); Runnable task = new Runnable() { @Override public void run() { try { my.waitUntilPropNotEmpty(); succeeded = true; } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }; Future<?> result = executor.submit(task); my.setMyProp(""); assertFalse(succeeded); my.setMyProp("test"); result.get(); //wait for end of task assertTrue(succeeded); }
  • 78. public class MyNewClassTest... private volatile boolean succeeded = false; @Test public void waitingForAValue() throws Exception { final MyNewClass my = new MyNewClass(); ExecutorService executor = Executors.newCachedThreadPool(); Runnable task = new Runnable() { @Override public void run() { try { my.waitUntilPropNotEmpty(); succeeded = true; } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }; Future<?> result = executor.submit(task); my.setMyProp(""); assertFalse(succeeded); my.setMyProp("test"); result.get(); //wait for end of task assertTrue(succeeded); }
  • 79. public class MyNewClassTest... private volatile boolean succeeded = false; @Test public void waitingForAValue() throws Exception { final MyNewClass my = new MyNewClass(); ExecutorService executor = Executors.newCachedThreadPool(); Runnable task = new Runnable() { @Override public void run() { try { my.waitUntilPropNotEmpty(); succeeded = true; } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }; Future<?> result = executor.submit(task); my.setMyProp(""); assertFalse(succeeded); my.setMyProp("test"); result.get(); //wait for end of task assertTrue(succeeded); }
  • 80. Atomics public class SynchronizedCounter... private long count = 0; public synchronized long value() { return count; } public synchronized void inc() { count++; }
  • 81. Atomics public class SynchronizedCounter... private long count = 0; public synchronized long value() { return count; } public synchronized void inc() { count++; } public class AtomicCounter... private AtomicLong count = new AtomicLong(0); public long value() { return count.longValue(); } public void inc() { count.incrementAndGet(); }
  • 82. Compare and Set public class HandmadeAtomicReference<V>... private V ref; public HandmadeAtomicReference(V initialValue) {...} public synchronized V get() { return ref; } /** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * @param expect the expected value * @param update the new value * @return true if successful. */ public synchronized boolean compareAndSet(V expect, V update) { if (ref != expect) return false; ref = update; return true; }
  • 83. Und noch mehr... • Concurrent Collections • BlockingQueue • Latches, Barriers, Exchangers & Co
  • 84. Mein Lieblingsbeispiel: Banking Bank createNewAccountFor(customer): Account accountByNumber(long accNum): Account allAccountNumbers(): Collection<long> Account accountNumber: long customer: String balance: BigDecimal deposit(amount) withdraw(amount)
  • 85. Mein Lieblingsbeispiel: Banking Bank createNewAccountFor(customer): Account accountByNumber(long accNum): Account allAccountNumbers(): Collection<long> uses Account MoneyTransfer accountNumber: long customer: String amount: BigDecimal balance: BigDecimal from, to: long deposit(amount) execute(Bank bank) withdraw(amount)
  • 86. public class Account { private BigDecimal balance = BigDecimal.ZERO; private final long accountNumber; private final String customer; public Account(long accountNumber, String customer) {...} public void deposit(BigDecimal amount) { balance = balance.add(amount); } public void withdraw(BigDecimal amount) { balance = balance.subtract(amount); } public BigDecimal getBalance() { return balance; } public long getAccountNumber() { return accountNumber; } public String getCustomer() { return customer; } }
  • 87. public class Bank { private Map<Long, Account> accounts = new HashMap<Long, Account>(); private long lastAccountNumber = 0; public Account createNewAccountForString(String customer) { Account account = new Account(++lastAccountNumber, customer); accounts.put(account.getAccountNumber(), account); return account; } public Account accountByNumber(long accountNumber) { return accounts.get(accountNumber); } }
  • 88. public class AccountCreationTest extends ParallelTestCase... private volatile Bank bank = new Bank(); @Test public void createAccountsInManyThreads() throws Exception { final int numberOfAccounts = 100; final AtomicInteger account = new AtomicInteger(0); Runnable createAccountTask = new Runnable() { @Override public void run() { String customer = "c" + account.incrementAndGet(); long accountNumber = bank.createNewAccountForString( customer).getAccountNumber(); assertEquals(customer, bank.accountByNumber( accountNumber).getCustomer()); } }; runInParallelThreads(numberOfAccounts, createAccountTask); assertAllAccountsHaveBeenCreated(numberOfAccounts); }
  • 89. "No account with number 99" public class Bank { private Map<Long, Account> accounts = new HashMap<Long, Account>(); private long lastAccountNumber = 0; public Account createNewAccountForString(String customer) { Account account = new Account(++lastAccountNumber, customer); accounts.put(account.getAccountNumber(), account); return account; } public Account accountByNumber(long accountNumber) { return accounts.get(accountNumber); } }
  • 90. public class Bank { private Map<Long, Account> accounts = new HashMap<Long, Account>(); private AtomicLong lastAccountNumber = new AtomicLong(0L); public Account createNewAccountForString(String customer) { Account account = new Account(lastAccountNumber.incrementAndGet(), customer); accounts.put(account.getAccountNumber(), account); return account; } public Account accountByNumber(long accountNumber) { return accounts.get(accountNumber); } }
  • 91. NullPointerException public class Bank { private Map<Long, Account> accounts = new HashMap<Long, Account>(); private AtomicLong lastAccountNumber = new AtomicLong(0L); public Account createNewAccountForString(String customer) { Account account = new Account(lastAccountNumber.incrementAndGet(), customer); accounts.put(account.getAccountNumber(), account); return account; } public Account accountByNumber(long accountNumber) { return accounts.get(accountNumber); } }
  • 92. public class Bank { private final Map<Long, Account> accounts = new ConcurrentHashMap<Long, Account>(); private final AtomicLong lastAccountNumber = new AtomicLong(0L); public Account createNewAccountForString(String customer) { Account account = new Account(lastAccountNumber.incrementAndGet(), customer); accounts.put(account.getAccountNumber(), account); return account; } public Account accountByNumber(long accountNumber) { return accounts.get(accountNumber); } }
  • 93. ok public class Bank { private final Map<Long, Account> accounts = new ConcurrentHashMap<Long, Account>(); private final AtomicLong lastAccountNumber = new AtomicLong(0L); public Account createNewAccountForString(String customer) { Account account = new Account(lastAccountNumber.incrementAndGet(), customer); accounts.put(account.getAccountNumber(), account); return account; } public Account accountByNumber(long accountNumber) { return accounts.get(accountNumber); } }
  • 94. public class Account... private final AtomicReference<BigDecimal> balance = new AtomicReference<BigDecimal>(BigDecimal.ZERO); public void deposit(BigDecimal amount) { while (true) { BigDecimal oldAmount = balance.get(); BigDecimal newAmount = oldAmount.add(amount); if (balance.compareAndSet(oldAmount, newAmount)) return; } } public void withdraw(BigDecimal amount) { while (true) { BigDecimal oldAmount = balance.get(); BigDecimal newAmount = oldAmount.subtract(amount); if (balance.compareAndSet(oldAmount, newAmount)) return; } } public BigDecimal getBalance() { return balance.get(); }
  • 95. public class Account... private final AtomicReference<BigDecimal> balance = new AtomicReference<BigDecimal>(BigDecimal.ZERO); public void deposit(BigDecimal amount) { while (true) { BigDecimal oldAmount = balance.get(); BigDecimal newAmount = oldAmount.add(amount); if (balance.compareAndSet(oldAmount, newAmount)) return; } } public void withdraw(BigDecimal amount) { while (true) { BigDecimal oldAmount = balance.get(); BigDecimal newAmount = oldAmount.subtract(amount); if (balance.compareAndSet(oldAmount, newAmount)) return; } } public BigDecimal getBalance() { return balance.get(); }
  • 96. public class Account... private BigDecimal balance = BigDecimal.ZERO; public synchronized void deposit(BigDecimal amount) { balance = balance.add(amount); } public synchronized void withdraw(BigDecimal amount) { balance = balance.subtract(amount); } public synchronized BigDecimal getBalance() { return balance; }
  • 97. public class MoneyTransfer { private final BigDecimal amount; private final long from; private final long to; public MoneyTransfer(BigDecimal amount, long from, long to) { this.amount = amount; this.from = from; this.to = to; } public void execute(Bank bank) { Account source = bank.accountByNumber(from); Account target = bank.accountByNumber(to); if (source.getBalance().compareTo(amount) < 0) throw new InsufficientFundsException(); source.withdraw(amount); target.deposit(amount); } }
  • 98. public class MoneyTransfer { private final BigDecimal amount; private final long from; private final long to; public MoneyTransfer(BigDecimal amount, long from, long to) { this.amount = amount; this.from = from; this.to = to; } synchronized bank) { public void execute(Bank void execute(Bank bank) { ( Account source = bank.accountByNumber(from); Account target = bank.accountByNumber(to); if (source.getBalance().compareTo(amount) < 0) throw new InsufficientFundsException(); source.withdraw(amount); target.deposit(amount); } }
  • 99. public class MoneyTransfer... public void execute(Bank bank) { Account source = bank.accountByNumber(from); Account target = bank.accountByNumber(to); synchronized (source) { synchronized (target) { doTransfer(source, target); } } } private void doTransfer(Account source, Account target) { if (source.getBalance().compareTo(amount) < 0) throw new InsufficientFundsException(); source.withdraw(amount); target.deposit(amount); }
  • 100. public class MoneyTransfer... public void execute(Bank bank) { Account source = bank.accountByNumber(from); Account target = bank.accountByNumber(to); synchronized (source) { synchronized (target) { doTransfer(source, target); ( } } } private void doTransfer(Account source, Account target) { if (source.getBalance().compareTo(amount) < 0) throw new InsufficientFundsException(); source.withdraw(amount); target.deposit(amount); } // Thread A: new MoneyTransfer(new BigDecimal(2), 234, 789).execute(bank); // Thread B: new MoneyTransfer(new BigDecimal(2), 789, 234).execute(bank);
  • 101. public class MoneyTransfer... public void execute(Bank bank) { Account source = bank.accountByNumber(from); Account target = bank.accountByNumber(to); Object[] locks = new Object[] { source, target }; Arrays.sort(locks); synchronized (locks[0]) { synchronized (locks[1]) { doTransfer(source, target); } } } public class Account implements Comparable<Account>... public int compareTo(Account other) { return new Long(accountNumber). compareTo(new Long(other.accountNumber)); }
  • 102. Für den normal sterblichen Entwickler ist die Erstellung korrekter Multi-Thread- Programme außerhalb ihrer Fähigkeiten
  • 103. public class ValueHolder { private List<Listener> listeners = new LinkedList<Listener>(); private int value; public static interface Listener { public void valueChanged(int newValue); } public void addListener(Listener listener) { listeners.add(listener); } public void setValue(int newValue) { value = newValue; for (Listener each : listeners) { each.valueChanged(newValue); } } } The Problem with Threads http://www.eecs.berkeley.edu/Pubs/TechRpts/2006/EECS-2006-1.pdf
  • 104. Zugrundeliegende Programmiermodell Shared mutable state (aka objects) is accessed in multiple concurrent threads, and we use locks to synchronize / sequentialize access to the state
  • 105. Das Objekt wird zur "undichten" Abstraktion Um aus einzelnen thread-sicheren Objekten komplexere thread-sichere Objekte zu bauen, muss der Locking-Mechanismus der Einzelobjekte bekannt sein ‣ Verletzt Prinzip der Kapselung ‣ Verletzt Prinzip der Modularisierung
  • 107. Alternative Paradigmen • Transactional Memory • Immutability • Message Passing • Parallel Collection Processing • Dataflow Concurrency
  • 108. Transactional Memory (TM) • Heap als transaktionale Datenmenge • Transaktionseigenschaften ähnlich wie bei einer Datenbank (ACID) • Optimistische Transaktionen • Transaktionen werden bei einer Kollision automatisch wiederholt • Transaktionen können geschachtelt und komponiert werden
  • 109. public class Account... public void deposit(BigDecimal amount) { atomic { balance = balance.add(amount); } } public void withdraw(BigDecimal amount) { atomic { balance = balance.subtract(amount); } }
  • 110. public class Account... public void deposit(BigDecimal amount) { atomic { balance = balance.add(amount); } } public void withdraw(BigDecimal amount) { atomic { balance = balance.subtract(amount); } }
  • 111. public class Account... public void deposit(BigDecimal amount) { atomic { balance = balance.add(amount); } } public void withdraw(BigDecimal amount) { atomic { balance = balance.subtract(amount); } } public class MoneyTransfer... public void execute(Bank bank) { atomic { Account source = bank.accountByNumber(from); Account target = bank.accountByNumber(to); if (source.getBalance().compareTo(amount) < 0) throw new InsufficientFundsException(); source.withdraw(amount); target.deposit(amount); } }
  • 112. Eigenschaften von TM • Vorteile: ‣ Wir können weiter in shared state und Transaktionen denken ‣ Die korrekte Verwendung ist wesentlich einfacher als bei Locks: Deadlocks sind ausgeschlossen! • Nachteile: ‣ Keinerlei Hilfe, wie wir unser sequenzielles Programm parallelisieren ‣ Der Fortschritt ist nicht garantiert. Livelocks sind möglich ‣ Die performante Implementierung ist noch Forschungsthema
  • 113. TM-Implementierungen • Hardware Transactional Memory • Software Transactional Memory ‣ Clojure: Programmiersprache auf der JVM, die STM eingebaut hat ‣ Java-STM-Frameworks: Deuce, Akka, Multiverse
  • 115. Immutability • Ohne veränderlichen Zustand, müssen Veränderungen auch nicht synchronisiert werden
  • 116. Immutability • Ohne veränderlichen Zustand, müssen Veränderungen auch nicht synchronisiert werden • Systeme haben aber doch einen Zustand der sich ändert, oder?
  • 117. Immutability • Ohne veränderlichen Zustand, müssen Veränderungen auch nicht synchronisiert werden • Systeme haben aber doch einen Zustand der sich ändert, oder? • Trick: Wir unterscheiden zwischen unveränderlichen Werten (values) und einer bestimmten Entity zugeordnenten aktuellen Wert
  • 118. @Immutable class Account { BigDecimal balance long accountNumber String customer static Account create(long accountNumber, String customer) { new Account(balance: 0.0, accountNumber: accountNumber, customer: customer) } Account deposit(amount) { cloneWithChange(balance: balance + amount) } Account withdraw(amount) { cloneWithChange(balance: balance - amount) } private Account cloneWithChange(changes) { def newProps = ... new Account(newProps) } }
  • 119. @Immutable class Account... BigDecimal balance long accountNumber String customer long version static Account create(long accountNumber, String customer) { new Account(balance: 0.0, accountNumber: accountNumber, customer: customer, version: 0) } Account incrementVersion() { cloneWithChange(version: version + 1) } Account deposit(amount) { cloneWithChange(balance: balance + amount) } Account withdraw(amount) { cloneWithChange(balance: balance - amount) }
  • 120. class Bank... private final accounts = new ConcurrentHashMap() Account createNewAccountFor(String customer) { Account account = Account.create(nextAccountNumber(), customer).incrementVersion(); accounts[account.getAccountNumber()] = account } Account accountByNumber(long accountNumber) { accounts[accountNumber] } synchronized boolean update(Account... accountsToUpdate) { if (accountsToUpdate.any {acc -> acc.version != accounts[acc.accountNumber]?.version }) { return false } accountsToUpdate.each {acc -> accounts[it.accountNumber] = it.incrementVersion() } return true }
  • 121. @Immutable class MoneyTransfer { BigDecimal amount long from, to boolean execute(Bank bank) { while(true) { Account source = bank.accountByNumber(from); Account target = bank.accountByNumber(to); if (source.balance < amount) return false source = source.withdraw(amount) target = target.deposit(amount) if (bank.update(source, target)) return true } } }
  • 122. Immutability im Großen • Performante Implementierung von "Immutable Data Types" ist möglich • In funktionale Programmiersprachen sind unveränderliche Werte die Norm und veränderlicher State die Ausnahme ‣ Beispiel Clojure: Fokus auf unveränderliche Werte und explizite Mechanismen zur Manipulation des aktuellen Werts einer Entity
  • 123. Message Passing - Actors • Vollständiger Verzicht auf "shared state" • Aktoren empfangen und verschicken Nachrichten ‣ Asynchron und nicht-blockierend ‣ Jeder Actor hat seine "Mailbox" • Immer nur ein aktiver Thread pro Aktor
  • 124. class BankActor extends AbstractPooledActor... void act() { loop { react { message -> switch(message) { case CreateAccount: createNewAccountFor(message.customer); break case GetAccount: accountByNumber(message.accountNumber); break case GetAllAccountNumbers: allAccountNumbers(); break case UpdateAccounts: update(message.accounts); break default: println "${this.class}: I don't understand $message" } } } } @Immutable class CreateAccount { String customer } @Immutable class UpdateAccounts { Account[] accounts } @Immutable class GetAccount { long accountNumber } @Immutable class GetAllAccountNumbers {}
  • 125. class BankActor extends AbstractPooledActor... private accounts = [:] private lastAccountNumber = 0 void createNewAccountFor(String customer) { Account account = ... reply account } void accountByNumber(long accountNumber) { reply accounts[accountNumber] } void allAccountNumbers() { reply accounts.keySet() as List } void update(Account[] accountsToUpdate) { if (accountsToUpdate.any { acc -> acc.version != accounts[acc.accountNumber]?.version }) { reply false } else { accountsToUpdate.each {acc -> accounts[acc.accountNumber] = acc.incrementVersion() } reply true } }
  • 126. class MoneyTransferActor... BankActor bank void act() { ... switch(message) { case TransferMoney: transferMoney(message); break; } void transferMoney(transfer) { while(true) { Account source = bank.sendAndWait(new GetAccount(transfer.from)); Account target = bank.sendAndWait(new GetAccount(transfer.to)); if (source.balance < transfer.amount) { reply new TransferResult(transfer.transferId, false) break } source = source.withdraw(transfer.amount) target = target.deposit(transfer.amount) if (bank.sendAndWait(new UpdateAccounts(accounts: [source, target]))) { reply new TransferResult(transfer.transferId, true) break } } } @Immutable class TransferMoney { long transferId BigDecimal amount long from, to }
  • 127. Actors - Vorteile • Unabhängige Aktoren • Skalierbar • Verteilbar • Leichtere Vermeidung von ‣ Race Conditions ‣ Dead Locks ‣ Starvation
  • 128. Fehler-tolerant & hoch-verfügbar • Mehrere redundante Aktoren stehen für die gleiche Aufgabe zur Verfügung • Organisation in Supervisor-Hierarchien ‣ Kein lokales Exception-Handling, sondern Neustart eines Aktors im Fehlerfalle
  • 129. Actors - Nachteile • Nicht geeignet für Probleme, die einen echten Konsens über gemeinsame Objekte erfordern • Kommunikation über asynchrone Nachrichten ist für viele Problemstellungen eine Komplizierung
  • 130. Parallele Collections • Wir arbeiten auf allen Elementen einer Collection gleichzeitig • Voraussetzung: Die Einzeloperationen sind unabhängig voneinander • Typische parallele Aktionen: ‣ Filtern ‣ Transformieren
  • 131. Beispiel: Standardabweichung aller Millionärskonten von 1.000.000 transform filter transform Kontonummer Saldo Mio-Saldo Diff2Mio transform filter transform Kontonummer transform Saldo filter Mio-Saldo transform Diff2Mio Kontonummer transform Saldo filter Mio-Saldo transform Diff2Mio Kontonummer transform Saldo filter Mio-Saldo transform Diff2Mio transform Saldo filter Mio-Saldo transform Diff2Mio sum Kontonummer Kontonummer transform Saldo filter Mio-Saldo transform Diff2Mio Kontonummer transform Saldo filter Mio-Saldo transform Diff2Mio Kontonummer transform Saldo filter Mio-Saldo transform Diff2Mio mier Kontonummer Saldo Mio-Saldo Diff2Mio su m en m ier en Std-Dev
  • 132. gpars' parallele Collections Parallelizer.doParallel(numThreads) { def allBalances = bank.allAccountNumbers().collectParallel { return bank.accountByNumber(it).balance } def millionaireBalances = allBalances.findAllParallel { it >= MILLION } def deviations = millionaireBalances.collectParallel { balance -> def diffToMean = balance - MILLION return (diffToMean * diffToMean) } def count = deviations.size() def average = Math.sqrt(deviations.sumParallel() / count) }
  • 134. ..work.. ..work.. ..work.. Collection Collection ..work.. ..work..
  • 135. ..work.. ..work.. ..work.. ..work.. ..work.. ..work.. Collection Collection ..work.. ..work.. ..work.. ..work..
  • 136. MapReduce • Von Google patentierter Ansatz zur verteilten Bearbeitung großer Datencluster • Name stammt von den map- und reduce- Funktionen funktionaler Sprachen • Idee: Einzelne Datensätze werden durch eine Pipeline von Arbeitsschritten transformiert (map) und am Ende zu einem Ergebnis zusammengeführt (reduce)
  • 137. Data Set Data Set Data Set Data Set Data Set Data Set
  • 138. Data Set map Data Set Data Set map Data Set Data Set map Data Set Data Set map Data Set Data Set map Data Set Data Set map Data Set
  • 139. Data Set map Data Set reduce Data Set map Data Set Data Set map Data Set reduce reduce Data Set map Data Set reduce Data Set map Data Set Result reduce Data Set map Data Set
  • 140. Data Set map Data Set reduce Data Set map Data Set Data Set map Data Set reduce reduce Data Set map Data Set reduce Data Set map Data Set Result reduce Data Set map Data Set
  • 141. Map-Reduce mit gpars Parallelizer.doParallel(numThreads) { def deviations = bank.allAccountNumbers().parallel.map {accountNumber -> bank.accountByNumber(accountNumber).balance }.filter { balance -> balance >= MILLION }.map {balance -> def diffToMean = balance - MILLION (diffToMean * diffToMean) } //def sum = deviations.sum() def sum = deviations.reduce {a,b -> a + b} def count = deviations.size() def average = Math.sqrt(sum / count) }
  • 142. Kritik an MapReduce • Patentierbarkeit (claim of novelty) wird angezweifelt • Wie breit ist der Problemraum, für den MapReduce sinnvoll angewandt werden kann? • Wie sinnvoll ist MapReduce im nicht verteilten Kontext?
  • 143. Performance-Vergleich • Hardware: ‣ Intel i7: 4 cores, 8 hyper threads • Wettbewerber ‣ Sequenziell ‣ Parallel Collections ‣ MapReduce • Vefahren ‣ jeweils 1 - 8 Threads ‣ jeweils 10 Samples ‣ 1000, 2000, 10.000, 50.000, 100.000 Konten
  • 144. Erwartete Beschleunigung 3.00 2.50 Beschleunigungsfaktor 2.00 1.50 1.00 0.50 0 1 2 3 4 5 6 7 8 Anzahl der Threads Sequential Parallel Collections MapReduce
  • 145. Beschleunigung bei 1000 Konten 2.00 1.50 Beschleunigungsfaktor 1.00 0.50 0 1 2 3 4 5 6 7 8 Anzahl der Threads Sequential Parallel Collections MapReduce
  • 146. Beschleunigung bei 2000 Konten 2.00 1.50 Beschleunigungsfaktor 1.00 0.50 0 1 2 3 4 5 6 7 8 Anzahl der Threads Sequential Parallel Collections MapReduce
  • 147. Beschleunigung bei 10000 Konten 2.00 1.50 Beschleunigungsfaktor 1.00 0.50 0 1 2 3 4 5 6 7 8 Anzahl der Threads Sequential Parallel Collections MapReduce
  • 148. Beschleunigung bei 50000 Konten 2.00 1.50 Beschleunigungsfaktor 1.00 0.50 0 1 2 3 4 5 6 7 8 Anzahl der Threads Sequential Parallel Collections MapReduce
  • 149. Beschleunigung bei 100000 Konten 2.00 1.50 Beschleunigungsfaktor 1.00 0.50 0 1 2 3 4 5 6 7 8 Anzahl der Threads Sequential Parallel Collections MapReduce
  • 150. Data Flows • Beschreibung der Abhängigkeiten von Daten untereinander • Grundkonzept: Dataflow-Variablen ‣ Wert kann nur einmal gebunden werden ‣ Abfrage des Wertes blockiert bis er gebunden wurde
  • 151. gpars - DataFlows def x = new DataFlowVariable() def y = new DataFlowVariable() def z = new DataFlowVariable() task { z << x.val + y.val } task { x << 40 } task { y << 2 } assert 42 == z.val
  • 152. gpars - DataFlows def x = new DataFlowVariable() def y = new DataFlowVariable() def z = new DataFlowVariable() task { z << x.val + y.val } task { x << 40 } task { y << 2 } assert 42 == z.val def df = new DataFlows() task { df.z = df.x + df.y } task { df.x = 40 } task { df.y = 2 } assert 42 == df.z
  • 153. Vorteile von Dataflows • Kein Locking notwendig • Keine Race-Conditions • Deterministisch ‣ Deadlocks sind möglich, sie passieren dann aber immer! • Kein Unterschied zwischen sequenziellem und nebenläufigem Code
  • 154. Beispiel: Depotwert def depot = ['AAPL':10, 'GOOG':2, 'IBM':20, 'ORCL':500] def stocks = depot.keySet() def prices = new DataFlows() def value = new DataFlowVariable() stocks.each {stock -> task { prices[stock] = getStockPrice(stock) } } task { value << stocks.collect { stock -> prices[stock] * depot[stock] }.sum() } println value.val
  • 155. Mehr über Data Flows • Einschränkungen ‣ Nicht für jede Problemstellung geeignet ‣ Berechnungen dürfen keine Seiteneffekte haben • Erweiterungen ‣ Data flow streams, Data flow operators • JVM-Implementierungen ‣ Scala Dataflow, FlowJava (akademisch & tot)
  • 156. Nicht behandelt • Fork/Join (jsr166y) • CSP: Communicating Sequential Processes • Declarative Concurrency • und andere...
  • 157. Fazit • Multi-thread-orientierte und Lock- basierte Programmierung ist schwierig ‣ Hauptgrund: Thread-sichere Objekte sind zusammengesetzt nicht komponierbar • "Immutability" erleichtert das parallele Leben sehr • Andere Paradigmen können helfen, der heilige Kral der Multi-Core-Entwicklung ist aber (noch) nicht gefunden
  • 158. Links Zum Lesen http://www.infoq.com/presentations/ goetz-concurrency-past-present http://www.slideshare.net/jboner/ state-youre-doing-it-wrong-javaone-2009 http://ajaxonomy.com/2008/news/ toward-better-concurrency http://igoro.com/archive/ gallery-of-processor-cache-effects/ Zum Ausprobieren http://gpars.codehaus.org/ http://clojure.org/ http://github.com/jboner/scala-dataflow

Hinweis der Redaktion

  1. Meine pers&amp;#xF6;nliche Geschichte beginnt im Jahre 2000. Es war das Jahr, in dem ich mich das erste mal ein intensiver mit Javas Multi-Threading-F&amp;#xE4;higkeiten auseinandergesetzt habe. Zuf&amp;#xE4;llig war das im Rahmen einer der ersten von mir mit TDD entwickelten Programmen, eine Logging-Framework.
  2. Diese Klasse enth&amp;#xE4;lt fast alles, was man fr&amp;#xFC;her (vor JDK 1.5) &amp;#xFC;ber Nebenl&amp;#xE4;ufigkeit und Synchronisation in Java wissen musste. Das volatile f&amp;#xFC;r das myProp-Member ist &amp;#xFC;brigens &amp;#xFC;berfl&amp;#xFC;ssig.
  3. Hier sieht man, wie man eigene Threads erzeugt, startet und auf ihr Ende wartet (join). Umfrage: Wer kennt die hier verwendeten Java-Konstrukte und wer nicht?
  4. Die Sprachkonstrukte waren also vergleichsweise wenige, beim Einsatz war ich schon immer sehr sparsam und habe mich auf die Position zur&amp;#xFC;ckgezogen: In der Regel brauche ich keine Nebenl&amp;#xE4;ufigkeit und wenn, dann erledigt das entweder ein Framework f&amp;#xFC;r mich (z.B. Web-Container verteilt Requests an Prozesse) oder - wenn sich Nebel&amp;#xE4;ufigkeit und Threads nicht vermeiden lassen, dann konzentriere ich mich darauf, Fachlogik von Synchronisation und Threading schon im Objektmodell eindeutig zu trennen, und dann denke ich mal ganz dolle &amp;#xFC;ber die wenigen nebenl&amp;#xE4;ufigen Klassen nach. Bis vor wenigen Jahren bin ich mit dieser Strategie auch gut gefahren, denn die Gelegenheiten, in den Nebenl&amp;#xE4;ufigkeit bei meinen Programmier- und Beratungsjobs zum Thema wurden, waren wenige, doch seit etwa 3 Jahren stolpere ich immer h&amp;#xE4;ufiger &amp;#xFC;ber Artikel, Vortr&amp;#xE4;ge und Frameworks zu diesem Thema. Ja, ganze Programmiersprachen und -Paradigmen (funktionale Programmierung) prahlen damit, dass sie sich perfekt f&amp;#xFC;r nebenl&amp;#xE4;ufige Prgrammierung eignen. Warum die Renaissance des Themas?
  5. Die Sprachkonstrukte waren also vergleichsweise wenige, beim Einsatz war ich schon immer sehr sparsam und habe mich auf die Position zur&amp;#xFC;ckgezogen: In der Regel brauche ich keine Nebenl&amp;#xE4;ufigkeit und wenn, dann erledigt das entweder ein Framework f&amp;#xFC;r mich (z.B. Web-Container verteilt Requests an Prozesse) oder - wenn sich Nebel&amp;#xE4;ufigkeit und Threads nicht vermeiden lassen, dann konzentriere ich mich darauf, Fachlogik von Synchronisation und Threading schon im Objektmodell eindeutig zu trennen, und dann denke ich mal ganz dolle &amp;#xFC;ber die wenigen nebenl&amp;#xE4;ufigen Klassen nach. Bis vor wenigen Jahren bin ich mit dieser Strategie auch gut gefahren, denn die Gelegenheiten, in den Nebenl&amp;#xE4;ufigkeit bei meinen Programmier- und Beratungsjobs zum Thema wurden, waren wenige, doch seit etwa 3 Jahren stolpere ich immer h&amp;#xE4;ufiger &amp;#xFC;ber Artikel, Vortr&amp;#xE4;ge und Frameworks zu diesem Thema. Ja, ganze Programmiersprachen und -Paradigmen (funktionale Programmierung) prahlen damit, dass sie sich perfekt f&amp;#xFC;r nebenl&amp;#xE4;ufige Prgrammierung eignen. Warum die Renaissance des Themas?
  6. Die Sprachkonstrukte waren also vergleichsweise wenige, beim Einsatz war ich schon immer sehr sparsam und habe mich auf die Position zur&amp;#xFC;ckgezogen: In der Regel brauche ich keine Nebenl&amp;#xE4;ufigkeit und wenn, dann erledigt das entweder ein Framework f&amp;#xFC;r mich (z.B. Web-Container verteilt Requests an Prozesse) oder - wenn sich Nebel&amp;#xE4;ufigkeit und Threads nicht vermeiden lassen, dann konzentriere ich mich darauf, Fachlogik von Synchronisation und Threading schon im Objektmodell eindeutig zu trennen, und dann denke ich mal ganz dolle &amp;#xFC;ber die wenigen nebenl&amp;#xE4;ufigen Klassen nach. Bis vor wenigen Jahren bin ich mit dieser Strategie auch gut gefahren, denn die Gelegenheiten, in den Nebenl&amp;#xE4;ufigkeit bei meinen Programmier- und Beratungsjobs zum Thema wurden, waren wenige, doch seit etwa 3 Jahren stolpere ich immer h&amp;#xE4;ufiger &amp;#xFC;ber Artikel, Vortr&amp;#xE4;ge und Frameworks zu diesem Thema. Ja, ganze Programmiersprachen und -Paradigmen (funktionale Programmierung) prahlen damit, dass sie sich perfekt f&amp;#xFC;r nebenl&amp;#xE4;ufige Prgrammierung eignen. Warum die Renaissance des Themas?
  7. Die Sprachkonstrukte waren also vergleichsweise wenige, beim Einsatz war ich schon immer sehr sparsam und habe mich auf die Position zur&amp;#xFC;ckgezogen: In der Regel brauche ich keine Nebenl&amp;#xE4;ufigkeit und wenn, dann erledigt das entweder ein Framework f&amp;#xFC;r mich (z.B. Web-Container verteilt Requests an Prozesse) oder - wenn sich Nebel&amp;#xE4;ufigkeit und Threads nicht vermeiden lassen, dann konzentriere ich mich darauf, Fachlogik von Synchronisation und Threading schon im Objektmodell eindeutig zu trennen, und dann denke ich mal ganz dolle &amp;#xFC;ber die wenigen nebenl&amp;#xE4;ufigen Klassen nach. Bis vor wenigen Jahren bin ich mit dieser Strategie auch gut gefahren, denn die Gelegenheiten, in den Nebenl&amp;#xE4;ufigkeit bei meinen Programmier- und Beratungsjobs zum Thema wurden, waren wenige, doch seit etwa 3 Jahren stolpere ich immer h&amp;#xE4;ufiger &amp;#xFC;ber Artikel, Vortr&amp;#xE4;ge und Frameworks zu diesem Thema. Ja, ganze Programmiersprachen und -Paradigmen (funktionale Programmierung) prahlen damit, dass sie sich perfekt f&amp;#xFC;r nebenl&amp;#xE4;ufige Prgrammierung eignen. Warum die Renaissance des Themas?
  8. &quot;The free lunch is over&quot;: Ein Artikel aus dem Jahre 2005 von Herb Sutter. In diesem Artikel besch&amp;#xE4;ftigt er sich mit Moores &quot;Gesetz&quot; aus dem Jahre 1965: Die Komplexit&amp;#xE4;t integrieter Schaltkreise verdoppelt sich etwa alle 2 Jahre. Dieses Beobachtung gilt 45 Jahre sp&amp;#xE4;ter immer noch. Wenn auch seit 2004 in ver&amp;#xE4;nderter Form...
  9. Vortrag von Intel auf OOP: H&amp;#xE4;tte man auch nach 2004 einfach weiterhin die Taktfrequenz erh&amp;#xF6;ht, damm w&amp;#xE4;re: - 2007 der Prozessor geschmolzen - 2009 der Prozessor die Oberfl&amp;#xE4;chentemperatur der Sonne erreicht worden Ein anderer interessanter Aspekt, den man aus der Kurve erahnen kann: Der Anstieg des Stromverbrauchs ist in etwa gleichzeitig mit der Taktfrequenz konstant geblieben. Folgerung: Erfolgreiche Multi-Core-Programmierung ist aktiver Umweltschutz, weil der Stromverbrauch pro Rechenzyklus bei niedrigerer Taktfrequenz und mehr Kernen deutlich geringer ist als bei h&amp;#xF6;herer Taktfrequenz und einem Kern!
  10. Und weil das nicht nur Herb Sutter erkannt hat, ist das Thema &quot;nebenl&amp;#xE4;ufige Programmierung&quot; (aka Multi-Core-Programming) seit wenigen Jahre auch immer st&amp;#xE4;rker auf Konferenzen vertreten, neue Programmiersprachen nehmen sich des Themas an, und Frameworks mit dem Fokus wachsen wie Pilze aus dem Boden. Grund Genug f&amp;#xFC;r einen Berater, der von seinem aktuellen Wissen lebt, sich das Thema neu anzuschauen. Ich habe daher vor etwa einem Jahr beschlossen, mir das Thema wieder von Grund auf neu zu erarbeiten.
  11. Goetz von 2006: Praktische Einf&amp;#xFC;hrung in die aktuelle Version der Java-Multi-Threading-Mechanismen und des java.util.concurrent-Pakete Herlihy &amp; Shavit von 2008: Gr&amp;#xFC;ndliche, aber auch in Teilen sehr theoretische Einf&amp;#xFC;hring in die Programmierung von Synchronisationsmechanismen wie z.B. Locks W&amp;#xE4;hrend das Goetz-Buch v.a. die Anwendung der Konstrukte beschreibt, konzentriert sich das Herlihy-Buch auf Theorie und Implementierung der Mechanismen.
  12. Goetz von 2006: Praktische Einf&amp;#xFC;hrung in die aktuelle Version der Java-Multi-Threading-Mechanismen und des java.util.concurrent-Pakete Herlihy &amp; Shavit von 2008: Gr&amp;#xFC;ndliche, aber auch in Teilen sehr theoretische Einf&amp;#xFC;hring in die Programmierung von Synchronisationsmechanismen wie z.B. Locks W&amp;#xE4;hrend das Goetz-Buch v.a. die Anwendung der Konstrukte beschreibt, konzentriert sich das Herlihy-Buch auf Theorie und Implementierung der Mechanismen.
  13. Punkt eins und zwei sind essentiell, damit wir unser Programm guten Gewissens abliefern k&amp;#xF6;nnen. Die dritte Eigenschaft jedoch ist der eigentliche Grund f&amp;#xFC;r das neue Interesse: Br&amp;#xE4;uchten wir nicht bessere Performance (mehr Logik wird in der gleichen absoluten Zeit abgearbeitet), dann k&amp;#xF6;nnten wir uns zur&amp;#xFC;cklehnen. Die ersten beiden Eigenschaften sind bei linearen Programmen vergleichsweise leicht zu erreichen. Die &quot;Illusion&quot; des sequenziell ablaufenden Programms macht es uns leicht, Fehler in der Logik zu erkennen (z.B. mit Hilfe von Unit Tests) und zu beseitigen. In Programmen mit mehreren gleichzeitig - oder quasi gleichzeitig - ablaufenden &quot;F&amp;#xE4;den&quot; ist das anders. Schauen wir uns die 3 Aspekte etwas genauer an.
  14. Punkt eins und zwei sind essentiell, damit wir unser Programm guten Gewissens abliefern k&amp;#xF6;nnen. Die dritte Eigenschaft jedoch ist der eigentliche Grund f&amp;#xFC;r das neue Interesse: Br&amp;#xE4;uchten wir nicht bessere Performance (mehr Logik wird in der gleichen absoluten Zeit abgearbeitet), dann k&amp;#xF6;nnten wir uns zur&amp;#xFC;cklehnen. Die ersten beiden Eigenschaften sind bei linearen Programmen vergleichsweise leicht zu erreichen. Die &quot;Illusion&quot; des sequenziell ablaufenden Programms macht es uns leicht, Fehler in der Logik zu erkennen (z.B. mit Hilfe von Unit Tests) und zu beseitigen. In Programmen mit mehreren gleichzeitig - oder quasi gleichzeitig - ablaufenden &quot;F&amp;#xE4;den&quot; ist das anders. Schauen wir uns die 3 Aspekte etwas genauer an.
  15. Punkt eins und zwei sind essentiell, damit wir unser Programm guten Gewissens abliefern k&amp;#xF6;nnen. Die dritte Eigenschaft jedoch ist der eigentliche Grund f&amp;#xFC;r das neue Interesse: Br&amp;#xE4;uchten wir nicht bessere Performance (mehr Logik wird in der gleichen absoluten Zeit abgearbeitet), dann k&amp;#xF6;nnten wir uns zur&amp;#xFC;cklehnen. Die ersten beiden Eigenschaften sind bei linearen Programmen vergleichsweise leicht zu erreichen. Die &quot;Illusion&quot; des sequenziell ablaufenden Programms macht es uns leicht, Fehler in der Logik zu erkennen (z.B. mit Hilfe von Unit Tests) und zu beseitigen. In Programmen mit mehreren gleichzeitig - oder quasi gleichzeitig - ablaufenden &quot;F&amp;#xE4;den&quot; ist das anders. Schauen wir uns die 3 Aspekte etwas genauer an.
  16. Was wir uns zun&amp;#xE4;chst klarmachen m&amp;#xFC;ssen: inc() ist f&amp;#xFC;r den Prozessor - und auch f&amp;#xFC;r die JVM - keine atomare Operation...
  17. Laufen die beiden Aufrufe hintereinander ab, dann ist alles wunderbar.
  18. Wenn es jedoch zu Verschachtelungen kommt, dann kann so etwas triviales wie das Hochz&amp;#xE4;hlen schon zu falschen Ergebnissen f&amp;#xFC;hren. In Zeiten von mehreren Kernen wird das Problem &amp;#xFC;brigens pl&amp;#xF6;tzlich viel relevanter. Bei einem Kern konnte das nur passieren, wenn zwischen den Aufrufen ein Threadwechsel stattgefunden hat, was vergleichsweise selten passiert. Bei tats&amp;#xE4;chlich gleichzeitiger Ausf&amp;#xFC;hrung ist der Konflikt um mehr als eine Gr&amp;#xF6;&amp;#xDF;enordnung wahrscheinlicher.
  19. Hier der allgemeine Fall. Und das ist zudem noch vereinfacht, weil tats&amp;#xE4;chlich nicht nur die Reihenfolge zwischen den Threads nicht bekannt ist, sonder tats&amp;#xE4;chlich auch die Reihenfolge IN EINEM THREAD anders sein kann, als es unser Programm nahelegt.
  20. Diese Umsortierung der einzelnen Schritte macht der Compiler - oder eventuell sogar der Prozessor - um die Ausf&amp;#xFC;hrungsgeschwindigkeit von Programmen zu optimieren. In einem sequenziellen Programm bekommen wir jedoch davon nichts mit, weil es f&amp;#xFC;r uns als Programmierer alles so arrangiert wird, dass wir es nicht mitbekommen! Wir leben in unserer eigenen kleinen Trueman-Show.
  21. Was bedeutet &quot;korrektes Verhalten&quot; bei nebenl&amp;#xE4;ufiger Ausf&amp;#xFC;hrung? In einem sequenziellen Programm sollte ein Objekt zwischen Methoden-Aufrufen einen sinnvollen Zustand haben. In einem nebenl&amp;#xE4;ufigen Programm tritt dieses &quot;dazwischen&quot; m&amp;#xF6;glicherweise nie ein. Stellen wir uns ein mengenwertiges Objekt vor, in das st&amp;#xE4;ndig Objekte eingef&amp;#xFC;gt und gel&amp;#xF6;scht werden. Welche Objekte sollen &quot;korrekterweise&quot; zu einem bestimmten Zeitpunkt enthalten sein? Was ist die korrekte Gr&amp;#xF6;&amp;#xDF;e (size) dieses Objekts zu einem bestimmten Zeitpunkt? Die Frage ist nicht allgemein zu beantworten, siehe &quot;The Art of Multiprocessor Programming&quot;
  22. Locks sind unser Mittel um die sequenzielle Illusion wieder so weit wie n&amp;#xF6;tig aufrecht erhalten zu k&amp;#xF6;nnen.
  23. Synchronized ist nichts anderes als Locking, bei dem this als Lock-Objekt benutzt wird. Warum ben&amp;#xF6;tigt die value()-Methode ein Lock? Locks haben neben der Sequenzialisierung eine zweite erw&amp;#xFC;nschte Wirkung; sie sorgen f&amp;#xFC;r die Publizierung des Objektzustandes (publishing). W&amp;#xE4;re das nicht so, dann m&amp;#xFC;sste die Variable count ein volatile-Attribut bekommen. Zu dem Thema kommen wir gleich noch...
  24. Deadlocks: Zu diesem Thema kommen wir noch. Sequenzialisierte Programmausf&amp;#xFC;hrung verhindert die Performance-Verbesserung. W&amp;#xE4;re dies nicht so, dann k&amp;#xF6;nnten wir f&amp;#xFC;r alle Methoden ein globales Lock-Objekt zur Synchronisation verwenden, und unsere Nebenl&amp;#xE4;ufigkeits-Probleme w&amp;#xE4;ren gel&amp;#xF6;st! TAS: test and set CAS: compare and swap
  25. Deadlocks: Zu diesem Thema kommen wir noch. Sequenzialisierte Programmausf&amp;#xFC;hrung verhindert die Performance-Verbesserung. W&amp;#xE4;re dies nicht so, dann k&amp;#xF6;nnten wir f&amp;#xFC;r alle Methoden ein globales Lock-Objekt zur Synchronisation verwenden, und unsere Nebenl&amp;#xE4;ufigkeits-Probleme w&amp;#xE4;ren gel&amp;#xF6;st! TAS: test and set CAS: compare and swap
  26. Deadlocks: Zu diesem Thema kommen wir noch. Sequenzialisierte Programmausf&amp;#xFC;hrung verhindert die Performance-Verbesserung. W&amp;#xE4;re dies nicht so, dann k&amp;#xF6;nnten wir f&amp;#xFC;r alle Methoden ein globales Lock-Objekt zur Synchronisation verwenden, und unsere Nebenl&amp;#xE4;ufigkeits-Probleme w&amp;#xE4;ren gel&amp;#xF6;st! TAS: test and set CAS: compare and swap
  27. Die Konsistenz zwischen den verschiedenen Caches und dem &quot;echten&quot; Speicher wird &amp;#xFC;ber sogenannte Cache Coherence Protokolle sichergestellt. Auf Maschinencode-Ebene werden so genannte Memory Barriers verwendet, um die Reihenfolge von Speicherzugriffen bestimmen zu k&amp;#xF6;nnen. Schlussfolgerung: Jede Operation, die (potenziell) Zugriff auf gemeinsamen Speicher ausf&amp;#xFC;hrt, ist Faktor 100 langsamer als eine rein thread-lokale Operation. Diese ist eine Angabe &amp;#xFC;ber den relativen Unterschied und hat sich in den letzten 20 Jahren kaum ge&amp;#xE4;ndert.
  28. Die Konsistenz zwischen den verschiedenen Caches und dem &quot;echten&quot; Speicher wird &amp;#xFC;ber sogenannte Cache Coherence Protokolle sichergestellt. Auf Maschinencode-Ebene werden so genannte Memory Barriers verwendet, um die Reihenfolge von Speicherzugriffen bestimmen zu k&amp;#xF6;nnen. Schlussfolgerung: Jede Operation, die (potenziell) Zugriff auf gemeinsamen Speicher ausf&amp;#xFC;hrt, ist Faktor 100 langsamer als eine rein thread-lokale Operation. Diese ist eine Angabe &amp;#xFC;ber den relativen Unterschied und hat sich in den letzten 20 Jahren kaum ge&amp;#xE4;ndert.
  29. Die Konsistenz zwischen den verschiedenen Caches und dem &quot;echten&quot; Speicher wird &amp;#xFC;ber sogenannte Cache Coherence Protokolle sichergestellt. Auf Maschinencode-Ebene werden so genannte Memory Barriers verwendet, um die Reihenfolge von Speicherzugriffen bestimmen zu k&amp;#xF6;nnen. Schlussfolgerung: Jede Operation, die (potenziell) Zugriff auf gemeinsamen Speicher ausf&amp;#xFC;hrt, ist Faktor 100 langsamer als eine rein thread-lokale Operation. Diese ist eine Angabe &amp;#xFC;ber den relativen Unterschied und hat sich in den letzten 20 Jahren kaum ge&amp;#xE4;ndert.
  30. Die Sequenzialisierung ist jedoch nicht das einzige Problem, das wir uns bei Multi-Core-Architekturen einhandeln... Warum m&amp;#xFC;ssen wir uns dar&amp;#xFC;ber Gedanken machen?
  31. Zur&amp;#xFC;ck zu unserem vereinfachten Hardware-Schaubild. 1. Ein Objekt, das nicht von mehreren Threads zugegriffen wird, muss im besten Fall lediglich im Prozessor &quot;leben&quot; uns sichtbar sein. Und genau daf&amp;#xFC;r optimieren die Compiler unseren Code! Nur manchmal, z.B. bei einem Kontext-Switch, ist die VM gezwungen, das Objekt tats&amp;#xE4;chlich in den Hauptspeicher zu bringen. 2. Ein Objekt, das von mehreren Threads zugegriffen wird, muss jedoch &amp;#xFC;berall sichtbar sein - und das m&amp;#xF6;glichst sofort und nach jeder &amp;#xC4;nderung. Die VM wei&amp;#xDF; aber ohne unsere Mithilfe nicht, wann wir ein Objekt auch in anderen Threads verwenden wollen. Diese Aufgabe nennt sich &quot;Publishing&quot; Korrektes &quot;Publishing&quot; von Objekten und ihres Zustands ist ein eigenes, nicht-triviales Thema f&amp;#xFC;r sich, das ich heute nur ganz am Rande ein paar mal streife. Wer sich im Detail damit besch&amp;#xE4;ftigen m&amp;#xF6;chte, dem steht ein harter Kampf mit Javas Memory-Modell bevor...
  32. Zur&amp;#xFC;ck zu unserem vereinfachten Hardware-Schaubild. 1. Ein Objekt, das nicht von mehreren Threads zugegriffen wird, muss im besten Fall lediglich im Prozessor &quot;leben&quot; uns sichtbar sein. Und genau daf&amp;#xFC;r optimieren die Compiler unseren Code! Nur manchmal, z.B. bei einem Kontext-Switch, ist die VM gezwungen, das Objekt tats&amp;#xE4;chlich in den Hauptspeicher zu bringen. 2. Ein Objekt, das von mehreren Threads zugegriffen wird, muss jedoch &amp;#xFC;berall sichtbar sein - und das m&amp;#xF6;glichst sofort und nach jeder &amp;#xC4;nderung. Die VM wei&amp;#xDF; aber ohne unsere Mithilfe nicht, wann wir ein Objekt auch in anderen Threads verwenden wollen. Diese Aufgabe nennt sich &quot;Publishing&quot; Korrektes &quot;Publishing&quot; von Objekten und ihres Zustands ist ein eigenes, nicht-triviales Thema f&amp;#xFC;r sich, das ich heute nur ganz am Rande ein paar mal streife. Wer sich im Detail damit besch&amp;#xE4;ftigen m&amp;#xF6;chte, dem steht ein harter Kampf mit Javas Memory-Modell bevor...
  33. aus Goetz06, S. 49ff Wann habe ich ein Objekt so erzeugt, dass es vollst&amp;#xE4;ndig in allen Threads sichtbar ist? Vollst&amp;#xE4;ndig hei&amp;#xDF;t, dass nicht nur das Objekt selbst, sondern auch sein innerer Zustand in allen Threads sichtbar ist. Unver&amp;#xE4;nderlich: alle Felder sind final. Dann ist das Objekt publiziert, auch wenn die Referenz darauf nicht final/volatile/atomic ist. Fallstrick: Leak object in constructor.
  34. Locking beim Zugriff: Auch, wenn du nur lesend zugreifst!
  35. Locking beim Zugriff: Auch, wenn du nur lesend zugreifst!
  36. Locking beim Zugriff: Auch, wenn du nur lesend zugreifst!
  37. Locking beim Zugriff: Auch, wenn du nur lesend zugreifst!
  38. So genannter lock-ordering deadlock: Wenn Locks in der falschen Reihenfolge angefordert werden, kann es zur Verklemmung kommen. Ein praxisn&amp;#xE4;heres Deadlock-Beispiel werden wir sp&amp;#xE4;ter noch sehen. Es gibt auch Livelocks: Ein Thread, obwohl er nicht blockiert ist, versucht immer wieder die gleiche Operation auszuf&amp;#xFC;hren, die immer wieder fehlschl&amp;#xE4;gt. Passiert z.B. wenn in einem transaktionalen Message-orientierten System eine Nachricht nicht abgearbeitet werden kann, und nach dem Rollback der Transaktion die gleiche Nachricht wieder an den Anfang der Queue gesetzt wird.
  39. Gr&amp;#xFC;nde: - Die Ressource ist ausgelastet. - Der Verteilungsmechanismus ist nicht fair. Locks sind in der Regel nicht fair implementiert, weil faire Implementierungen eine schlechtere Performance haben!
  40. Responsiveness: Vor allem in GUI-Applikationen von Bedeutung. Auch mit einem Kern zu erreichen. Erfordert v.a. thread-sicheres Programmieren, damit das Starten von parallelen Threads gefahrlos m&amp;#xF6;glich ist. Bei Swing-Applikationen muss man aufpassen, weil Swing von sich aus nicht thread-safe ist und Oberfl&amp;#xE4;chenmanipulationen in AWT-Thread erfolgen m&amp;#xFC;ssen. F&amp;#xFC;r die Thread-Sicherheit der darunterliegenden Fachobjekte muss man als Entwickler selbst sorgen! Throughput: Dies ist tats&amp;#xE4;chlich nur bei mehreren Kernen/Prozessoren sinnvoll. Eine grunds&amp;#xE4;tzliche Aussage dazu macht Amdahls Law...
  41. AL liefert eine obere Grenze f&amp;#xFC;r die erreichbare Beschleunigung eines Programms. Vereinfachte Annahme: Ein (Teil-)Programm ist in einen beliebig parallelisierbaren und in einen nicht parallelisierbaren Anteil aufteilbar. Dann wird die maximal erreichbare Beschleunigung durch den nicht parallelisierbaren Anteil begrenzt.
  42. Hier die sehr optimistische Annahme, dass nur 20% des Programms nicht parallelisierbar sind. Dann kann man auch mit unendlich vielen Prozessoren nur auf eine Beschleunigung des Faktors 5 kommen. Und das ist das ist die maximale Obergrenze der Beschleunigung!
  43. Aus einem linearen Algorithmus einen parallelen zu machen ist ein nicht-triviales Problem f&amp;#xFC;r das es keine allgemeine L&amp;#xF6;sung gibt. Zwei Standardstrategien existieren.
  44. Aus einem linearen Algorithmus einen parallelen zu machen ist ein nicht-triviales Problem f&amp;#xFC;r das es keine allgemeine L&amp;#xF6;sung gibt. Zwei Standardstrategien existieren.
  45. Strategie 1: Wir schauen welche Schritte des Algorithmus unabh&amp;#xE4;ngig voneinander sind und parallel abgewickelt werden k&amp;#xF6;nnen. Daf&amp;#xFC;r gibt es auch Formalisierungsmechanismen &amp;#xFC;ber Abh&amp;#xE4;ngigkeitsgrafen und Petrinetze. Klassisches Lehrbuchbeispiel ist Kochrezept. Erfordert sehr viel Handarbeit und skaliert meist nicht f&amp;#xFC;r eine hohe Anzahl von Prozessoren.
  46. Strategie 2: Ersetzen des Algorithmus oder eines Teils davon durch einen rekursiv zerlegbaren Algorithmus. Die optimale Anzahl der Zerlegungen ist dann abh&amp;#xE4;ngig von Anzahl der Kerne und der Kosten f&amp;#xFC;r Zerlegung und Zusammenbringung =&gt; Fork/Join Diese Strategie skaliert meist auch f&amp;#xFC;r eine hohe Anzahl von Kernen, aber sie existiert nicht f&amp;#xFC;r alle Probleme.
  47. Datenparallelisierung ist manchmal sehr einfach, wenn die Berechnung am einzelnen Datenelement unabh&amp;#xE4;ngig von den anderen Datenelementen ist. Map-Reduce ist einer der Ans&amp;#xE4;tze, die hier eine Rolle spielen. Sowohl bei Kontrollfluss-Parallelisierung als auch bei Datenparallelisierung spielen Safety und Liveness eine Rolle, sobald die nebenl&amp;#xE4;ufig arbeitenden Einheiten auf gemeinsame, ver&amp;#xE4;nderbare Objekte zugreifen. Bei Datenparallelisierung versucht man jedoch idR das Schneiden der Daten so vorzunehmen, dass es keinen gemeinsam genutzten Zustand mehr gibt.
  48. Mit Version 5 wurde Java erweitert um drei Pakete, die sich dem Thema parallele Programmierung widmen. Im Gro&amp;#xDF;en und Ganzen enth&amp;#xE4;lt j.u.concurrent die Erweiterungen, die Doug Lean schon als zus&amp;#xE4;tzliches Paket vor 10 Jahren geschaffen hat, das dem Java-Programmierer die allt&amp;#xE4;glichen Aufgaben im Zusammenhang mit Multi-Threading und Synchronisation erleichtern sollen.
  49. Explizite Locks und Conditions
  50. Muster zur Verwendung von expliziten Locks: try-finally.
  51. Muster zur Verwendung von expliziten Locks: try-finally.
  52. Muster zur Verwendung von expliziten Locks: try-finally.
  53. Impizite Locks sind leichter zu verwenden, da man sich das try-finally sparen kann.
  54. Die Klasse Executors bietet zahlreiche M&amp;#xF6;glichkeiten, sich ExecutorService-Objekte zu erzeugen. Die gezeigte Variante benutzt Threads wieder, die nicht mehr ben&amp;#xF6;tigt werden, expandiert aber die Anzahl der Threads wenn n&amp;#xF6;tig. Ein Future ist eine Referenz auf ein zuk&amp;#xFC;nftiges Ergebnis einer Berechnung. Das submit ist nicht blockierend, aber das get() blockiert bis der Task erledigt wurde. new Thread sollte man eigentlich nur noch benutzen, wenn man sich seinen eigenen Executor baut, oder wenn man in Thread-Pools eigene Thread-Factories ben&amp;#xF6;tigt, z.B. f&amp;#xFC;r Testing oder Profiling.
  55. Die Klasse Executors bietet zahlreiche M&amp;#xF6;glichkeiten, sich ExecutorService-Objekte zu erzeugen. Die gezeigte Variante benutzt Threads wieder, die nicht mehr ben&amp;#xF6;tigt werden, expandiert aber die Anzahl der Threads wenn n&amp;#xF6;tig. Ein Future ist eine Referenz auf ein zuk&amp;#xFC;nftiges Ergebnis einer Berechnung. Das submit ist nicht blockierend, aber das get() blockiert bis der Task erledigt wurde. new Thread sollte man eigentlich nur noch benutzen, wenn man sich seinen eigenen Executor baut, oder wenn man in Thread-Pools eigene Thread-Factories ben&amp;#xF6;tigt, z.B. f&amp;#xFC;r Testing oder Profiling.
  56. Die Klasse Executors bietet zahlreiche M&amp;#xF6;glichkeiten, sich ExecutorService-Objekte zu erzeugen. Die gezeigte Variante benutzt Threads wieder, die nicht mehr ben&amp;#xF6;tigt werden, expandiert aber die Anzahl der Threads wenn n&amp;#xF6;tig. Ein Future ist eine Referenz auf ein zuk&amp;#xFC;nftiges Ergebnis einer Berechnung. Das submit ist nicht blockierend, aber das get() blockiert bis der Task erledigt wurde. new Thread sollte man eigentlich nur noch benutzen, wenn man sich seinen eigenen Executor baut, oder wenn man in Thread-Pools eigene Thread-Factories ben&amp;#xF6;tigt, z.B. f&amp;#xFC;r Testing oder Profiling.
  57. Oben: Die &quot;alte&quot;Variante, um den Counter thread-sicher zu machen. Warum gen&amp;#xFC;gt ein volatile bei count nicht? Weil der Prozessor f&amp;#xFC;r das Inkrementieren eines long-Werts im Speicher mehrere Operationen ben&amp;#xF6;tigt. ... Java bietet jetzt &quot;atomare Container&quot; f&amp;#xFC;r Integers, Longs und Booleans an, mit einer Menge von atomaren Operationen, z.B. incrementAndGet(). Diese atomaren funktionen basieren auf CAS. Wie funktioniert das?
  58. Die tats&amp;#xE4;chliche Implementierung ist wesentlich effizienter, denn sie beruht auf den entsprechenden primitiven Operationen des Prozessors und ist nicht-blockierend. Atomics k&amp;#xF6;nne auch f&amp;#xFC;r komplexere Synchronisationsprobleme verwendet werden. Wir werden sp&amp;#xE4;ter ein Beispiel f&amp;#xFC;r die Verwendung sehen. Regel aus Goetz06: In den meisten F&amp;#xE4;llen sind Atomics die bessere Alternative f&amp;#xFC;r volatile, denn nur selten gen&amp;#xFC;gt uns das ordungsgem&amp;#xE4;&amp;#xDF;e Publizieren eines Objekts, meistens m&amp;#xFC;ssen wir auch noch weitergehend synchronisieren und das geht h&amp;#xE4;ufig mit atomics besser.
  59. Schauen wir uns den praktischen Einsatz an meinem Lieblingsbeispiel, dem Konto, an.
  60. Testfall erzeugt parallel 100 Konten und greift gleich wieder darauf zu. ParallelTestCase ist eine spezielle Testklasse f&amp;#xFC;r nebenl&amp;#xE4;ufiges Testen. Test schl&amp;#xE4;gt mit unterschiedlichen Fehler fehl, z.B. &quot;No account with number 99&quot;...
  61. Der Grund ist wohl die nicht atomare Ausf&amp;#xFC;hrung von &quot;++lastAccountNumber&quot;. Daran w&amp;#xFC;rde ein &quot;volatile&quot; nat&amp;#xFC;rlich auch nichts &amp;#xE4;ndern.
  62. Umstellung auf AtomicLong beseitigt zwar den ersten Fehler f&amp;#xFC;hrt aber zu einem anderen.... Dies liegt daran, dass die Klasse HashMap nicht thread-safe ist und dadurch manchmal schon auf Werte zugegriffen wird, deren Key bereits existiert, deren Value aber noch nicht sichtbar ist.
  63. Benutzung einer thread-sicheren Map-Implementierung bringt uns endlich den erhofften gr&amp;#xFC;nen Balken. Wenn wir jedoch die korrekte Ver&amp;#xF6;ffentlich unserer beiden Members sicherstellen wollen, dann m&amp;#xFC;ssen wir diese auch noch final machen. Diese Ungenauigkeit hat unser Test &amp;#xFC;brigens nicht entdeckt. Das ist typisch: Viele Nebenl&amp;#xE4;ufigkeitsprobleme sind zun&amp;#xE4;chst mal nur potenzielle Probleme und f&amp;#xFC;hren erst bei einem Plattformwechsel oder dem Einsatz zus&amp;#xE4;tzlicher Prozessoren oder einem anderen Nutzungsprofil zu tats&amp;#xE4;chlichen Fehlern im Programmverhalten. Die Alternative zu dieser L&amp;#xF6;sung w&amp;#xE4;re eine explizite oder implizite Synchronisation der beiden Methoden. Dies w&amp;#xE4;re auch korrekt, sogar ohne final-Modifier, w&amp;#xE4;re aber bei starker Contention vermutlich langsamer, denn unsere jetzige Implementierung verzichtet vollst&amp;#xE4;ndig auf blockierende Synchronisation, denn unter der Haube benutzt ConcurrentHashMap auch Atomics.
  64. Widmen wir uns dem Account und erm&amp;#xF6;glichen nebenl&amp;#xE4;ufiges Einzahlen und Abheben. Hier sehen wir den typischen Einsatz der compareAndSet-Methode zur Synchronisation von Zugriffen auf ein komplexes Objekt. Im Falle der Kollision zweier Zugriffe, f&amp;#xFC;hren wir die Operation einfach nochmal durch. Das ist performance-m&amp;#xE4;&amp;#xDF;ig sehr geschickt, da lediglich bei echter Contention zus&amp;#xE4;tzlicher Code ausgef&amp;#xFC;hrt wird. Diesen Ansatz kann man auch verfolgen, wenn man mehr als eine Member-Variable atomar zugreifen m&amp;#xF6;chte, indem man die zusammengeh&amp;#xF6;rigen Variablen in eine Value-Klasse zusammenfasst und die Referenz darauf &amp;#xFC;ber eine AtomicReference sch&amp;#xFC;tzt. Diese L&amp;#xF6;sung hat aber auch einen Nachteil: Dieser interne Sync-Mechanismus kann nicht von au&amp;#xDF;en benutzt werden, wenn man beispielsweise atomare Operationen au&amp;#xDF;erhalb der Klasse anbieten m&amp;#xF6;chte, oder wenn man mehrere Accounts gleichzeitig bearbeiten m&amp;#xF6;chte, z.B. bei einer &amp;#xDC;berweisung. Daher w&amp;#xE4;hlen wir in diesem Fall die klassische synchronized-Variante.
  65. Auch hier wieder daran denken, dass auch getBalance() durch synchronized gesch&amp;#xFC;tzt werden muss. Kommen wir zur &amp;#xDC;berweisung...
  66. Fachlich wollen wir das hier: Eine Klasse, der wir Betrag und Kontonummern geben k&amp;#xF6;nnen und die dann auf Befehl die &amp;#xDC;berweisung durchf&amp;#xFC;hrt, bzw. eine entsprechende Exception wirft, falls der &amp;#xDC;berweisungsbetrag nicht gedeckt ist. Als Profis erkennen wir nat&amp;#xFC;rlich gleich, dass die execute-Methode nicht thread-sicher ist. Da wir sicherstellen wollen, dass eine &amp;#xDC;berweisung ein atomarer Vorgang ist. Und wir erkennen auch, dass das einfache synchronized hier nicht gen&amp;#xFC;gt, weil wir ja nicht auf MoneyTransfer synchronisieren m&amp;#xFC;ssen, sondern auf die beteiligten Konten.
  67. Unser MoneyTransfer sieht also so aus. Sobald wir gleichzeitig mehr als einen Lock halten, m&amp;#xFC;ssen wir nach potenziellen Deadlocks Ausschau halten. Ich habe vorhin den klassischen Deadlock durch inkonsistente Locking-Reihenfolge gezeigt. Der trifft hier nicht zu, denn wir locken ja konsistent immer zuerst das Quell-Konto und dann das Ziel-Konto. ... Oder vielleicht doch nicht?
  68. Wir m&amp;#xFC;ssen einen Mechanismus finden, der daf&amp;#xFC;r sorgt, dass wir Locks immer in der gleichen Reihenfolge anfragen! Hier bietet sich die Kontonummer als Kriterium an, wenn es keinen fachlichen Ordnungsmechnismus gibt, kann man z.B. auch den System-weiten identityHashCode benutzen. Puh, das war harte Arbeit, und dabei hatten wir ein vergleichsweise einfaches Problem vor uns.
  69. Um systemweit sauber und thread-sicher zu arbeiten, ben&amp;#xF6;tigt man viel internes Wissen und mehrere Dutzend Regeln und Heuristiken. Wir k&amp;#xF6;nnen zu viele Locks setzen, zu wenige, die falschen. Es gibt hunderte Details, die wir beachten m&amp;#xFC;ssen. Nach der Lekt&amp;#xFC;re von &quot;Java concurrency in Practice&quot; bleibt das Gef&amp;#xFC;hl zur&amp;#xFC;ck, dass ich - erstens nicht jeden Aspekt der Problematik verstehe (insbesondere bei Details des Java Memory Modells) - zweitens nicht alle Regeln konsistent werde umsetzen k&amp;#xF6;nnen, schon gar nicht in einem Team oder &amp;#xFC;ber Team-Grenzen hinweg Zur obigen Erkenntnis kommen die meisten normal-sterblichen Programmierer, wenn sie sich &quot;ausreichend&quot; mit dem Thema besch&amp;#xE4;ftigt haben. Wer zu einer anderen Erkenntnis gelangt, hat sich entweder noch nicht ausreichend damit besch&amp;#xE4;ftigt ... oder ist ein L&amp;#xFC;gner.
  70. Als &amp;#xDC;bung empfohlen: Diese klassische Observer-Implementierung thread-sicher machen - und dann den Artikel lesen.
  71. Das Denken in dieser Abstraktion hat zwei Vorteile: - Es f&amp;#xE4;llt all denen leicht, die bisher v.a. in Objekten und sequenziell ausgef&amp;#xFC;hrten Methoden gedacht haben, also den meisten von uns - Es ist als Plattform (z.B. als JVM-Implementierungen) leicht zu implementieren, weil es sehr eng verwandt ist mit den hardware- und OS-seitigen Mechanismen.
  72. &quot;Thread-Synchronisation is not composable&quot; Damit verlieren wir einen der Hauptvorteile von Objektorientierung, die uns ja gerade verspricht, dass wir ein komplexes System aus vielen einfacheren Untersystemen, Objekten, zusammenbauen k&amp;#xF6;nnen, ohne dass wir die Implementierungsdetails der einzelnen Objekte kennen m&amp;#xFC;ssen. Im Kontext der Nebenl&amp;#xE4;ufigkeit wird die Abstraktion vom Objekt, das seinen inneren Zustand durch Methoden zugreifbar und ver&amp;#xE4;nderbar macht, zu einer &quot;leaky abstraction&quot; (undichte Abstraktion), weil wir pl&amp;#xF6;tzlich Wissen &amp;#xFC;ber die zugrundeliegende Implementierung brauchen, um Objekte miteinander kombinieren zu k&amp;#xF6;nnen.
  73. Ich kann die Frage hier und heute nicht beantworten, aber ich m&amp;#xF6;chte ein paar der Alternativen zeigen, die zur Zeit als Kandidaten gehandelt werden.
  74. Das ist keine vollst&amp;#xE4;ndige Liste, sondern eine Auswahl von Ans&amp;#xE4;tzen, mit denen ich mich besch&amp;#xE4;ftigt habe. Mit manchen mehr, mit anderen weniger. Es gibt auch hier noch andere, z.B. CSP (Communicating sequential processes)
  75. A: atomic, C: consistent, I: isolated, aber nicht D: durable, alternativ V: visible =&gt; ASIV Einschr&amp;#xE4;nkungen: Transaktionale Bereiche d&amp;#xFC;rfen - au&amp;#xDF;er der Ver&amp;#xE4;nderung von State - keine Seiteneffekte haben!
  76. Hier in Pseudo-Notation. Das Transfer-Objekt muss nichts dar&amp;#xFC;ber wissen, welche Methoden des Kontos bereits atomar sind.
  77. Hier in Pseudo-Notation. Das Transfer-Objekt muss nichts dar&amp;#xFC;ber wissen, welche Methoden des Kontos bereits atomar sind.
  78. Hier in Pseudo-Notation. Das Transfer-Objekt muss nichts dar&amp;#xFC;ber wissen, welche Methoden des Kontos bereits atomar sind.
  79. HTM: Es gibt bislang keinen Prozessor in Serienfertigung, der das Konzept unterst&amp;#xFC;tzt, aber prinzipiell ist das einfacher als man vermuten k&amp;#xF6;nnte, weil viele der Mechanismen, die man f&amp;#xFC;r die Umsetzung ben&amp;#xF6;tigt, bereits in den Cache-Coherence-Algorithmen vorhanden sind. STM: Die Implementierung von STM in imperativen Sprachen ist aktives Forschungsthema sowohl Akademisch als auch in der industriellen Forschung.
  80. Jede ver&amp;#xE4;ndernde Operation erzeugt eine neue Version des Kontos. Auf diese Weise k&amp;#xF6;nnen beliebig viele Threads auf einem Konto arbeiten, ohne dass sie sich dabei in die Quere k&amp;#xE4;men. Irgendwann jedoch m&amp;#xF6;chten wir den ver&amp;#xE4;nderten Wert eines Kontos wieder als aktuellen Wert festlegen, und damit wir dann wissen, auf welche urspr&amp;#xFC;ngliche Zustands-Version wir aufgesetzt haben, geben wir unserem Account-Objekt noch einen Versionsz&amp;#xE4;hler mit...
  81. Dieses Account-&quot;Objekt&quot; ist jetzt keine Entit&amp;#xE4;t im OO-Sinne mehr, sondern lediglich eine Zustandswert eines Objektes. Die eigentlichen Konto-Identit&amp;#xE4;ten werden in der Bank gehalten. Die Bank muss nun aber eine Schnittstelle zur Verf&amp;#xFC;gung stellen, um den Zustand eines - oder mehrer Konten - aktualisieren zu k&amp;#xF6;nnen...
  82. Der zu synchronisierende Code beschr&amp;#xE4;nkt sich hier auf ein Minimum, n&amp;#xE4;mlich die update-Methode: Diese bekommt als Parameter ein oder mehrer Konto-Wertobjekte, &amp;#xFC;berpr&amp;#xFC;ft dann, ob sich alle neuen Werte auf die aktuelle Basis-Version beziehen, und wenn ja, dann werden allen Konto-Entit&amp;#xE4;ten neue Konto-Werte zugewiesen und dabei noch die Versionsnummer hochgez&amp;#xE4;hlt. Falls vorher bereits ein oder mehrere Werte ge&amp;#xE4;ndert wurden, bekommt der Aufrufer die R&amp;#xFC;ckmeldung, dass der Update nicht geklappt hat, und dass er es nochmal versuchen kann - wenn er will.
  83. Wie w&amp;#xFC;rde also unsere &amp;#xDC;berweisung in diesem Fall aussehen? Kein Locking mehr notwendig! Typisches Muster f&amp;#xFC;r Lock-freie Updates: Wir wiederholen unsere Aktion so lange bis sie erfolgreich ist. Im Normalfall, ohne Kollision, hat der Aufruf keinerlei overhead f&amp;#xFC;r Synchronisation!
  84. So wie ich Account implementiert habe, findet bei jeder Ver&amp;#xE4;nderung des Account-Objektes eine komplette Kopie statt. Das ist nicht sehr effizient - in vielen F&amp;#xE4;llen aber egal! Es ist jedoch m&amp;#xF6;glich, dieses &quot;Kopieren&quot; sehr effizient zu machen, was Speicher und Rechenzeit angeht. Im Allgemeinen gen&amp;#xFC;gt es n&amp;#xE4;mlich, wenn die Kopie einen Verweis auf das &quot;Original&quot; bekommt und lediglich der ver&amp;#xE4;nderte Knoten des Wertebaums ausgetauscht wird. (siehe Persistent Data Structures von Clojure) Es gibt noch einige andere Ans&amp;#xE4;tze, wie man mit unver&amp;#xE4;nderlichen Werten und echten Funktionen zustandsbehaftete Systeme beschreiben kann. Wer sich daf&amp;#xFC;r interessiert sollte nach dem Stichwort &quot;streams in functional programming&quot; googlen.
  85. Bekannt geworden sind Actors durch Erlang (entstanden bei Ericsson), eine vergleichsweises esoterische Programmiersprache, die nicht zuletzt wegen ihres Aktoren-Modells gerade im Aufwind ist. Die Idee ist, dass das Threading-Modell sehr leichtgewichtig ist, damit man tausende oder auch Millionen von parallelen Aktoren haben kann. Aktoren sind sehr nah an der urspr&amp;#xFC;nglichen Idee von Objekt-Orientierung (aus Simula und sp&amp;#xE4;ter Smalltalk), in der Objekte lediglich &amp;#xFC;ber das Versenden von Nachrichten und den Empfang von Antworten miteinander kommunizieren. Auch Scala hat eine native Bibliothek f&amp;#xFC;r Threads. Was man gleich erkennt ist, dass Aktoren nicht f&amp;#xFC;r Probleme geeignet sein k&amp;#xF6;nnen, in denen mehrere Aktoren zu einem Konsens &amp;#xFC;ber gemeinsam benutzte Objekte kommen m&amp;#xFC;ssen. Dann m&amp;#xFC;ssen wir n&amp;#xE4;mlich mit komplizierten Protokollen arbeiten, um den Zustand wiederum zu synchronisieren. Wir alle kennen das Problem bei unserer Adresssynchronisation zwischen Rechner und Handy. Beispiel: Die zustandsbehafteten Konten auf jeweils eigene Aktoren zu verteilen ist eine schlechte Idee, wenn wir gleichzeitig konten&amp;#xFC;bergreifende atomare Operationen anbieten m&amp;#xFC;ssen - wie die &amp;#xDC;berweisung. Wir k&amp;#xF6;nnen aber von unserem letzten Ansatz der unver&amp;#xE4;nderlichen Account-Objekte ausgehen und schauen, ob wir dort einen sinnvollen Einsatz f&amp;#xFC;r Aktoren finden...
  86. Hier ein gpars-Actor: - Muss die act-Methode implementieren - Loop() hei&amp;#xDF;t, dass jede Nachricht auf die gleiche Weise behandelt wird - im react-Block habe ich Zugriff auf das ankommende Nachricht-Objekt. Hier mache ich nur einen dispatch anhand des Nachrichtentyps
  87. Die Implementierung der eigentlichen Fachlichkeit kommt jetzt vollst&amp;#xE4;ndig ohne Synchronisation aus. Da wir lediglich Nachrichten verschicken k&amp;#xF6;nne wir nicht einfach einen Wert zur&amp;#xFC;ckgeben, sondern m&amp;#xFC;ssen hier &quot;reply&quot; verwenden.
  88. Jetzt k&amp;#xF6;nnen wir einen Actor definieren, um &amp;#xDC;berweisungen auszuf&amp;#xFC;hren. Hier finden mehrere quasi-synchrone Aufrufe statt (send and wait). Das st&amp;#xF6;rt jedoch die Skalierbarkeit nicht wirklich, weil wir sehr viele dieser Aktoren gleichzeitig starten k&amp;#xF6;nnen!
  89. Unabh&amp;#xE4;ngigkeit: Leichtere Testbarkeit und h&amp;#xF6;heres Abstraktionslevel Skalierbar: Wenn geschickt implementiert Verteilbar: Kommunikation lediglich &amp;#xFC;ber asynchrone Nachrichten Vermeidung von ... aber nicht Verhinderung!
  90. Aktoren erm&amp;#xF6;glichen den Aufbau extrem verl&amp;#xE4;sslicher Systeme. Ericsson soll mit Erlang-basierten Telefonanlagen eine Verf&amp;#xFC;gbarkeit von 9 Neunen erreichen.
  91. Probleme, die echte atomare Transaktionen erfordern bieten sich nicht an. Wir kennen alle die Schwierigkeiten bei verteilten Transaktionen (2-Phasen-Commit). Overhead der asynchronen Kommunikation =&gt; Aktoren sind f&amp;#xFC;r nicht-verteilte Anwendungen meist nicht die einfachste Abstraktion. Gef&amp;#xFC;hlt: Aktoren bekommen im Zusammenhang von Multi-Core-Programmierung mehr Aufmerksamkeit als ihnen zusteht. Implemetierungen f&amp;#xFC;r die JVM gibt es mindestens ein halbes Dutzend: Neben gpars und der nativen Scala-Bibliothek, u.a. Akka und Jetlang
  92. gpars biete die Iterator-Methoden &amp;#xFC;ber Collections in einer parallelen Variante an: findAll, collect etc. Aber auch Summierung kann teilweise parallel laufen, da immer paarweise addiert werden kann und das Ergebnis auch wieder paarweise usw.
  93. Schauen wir uns an, was hier passiert... Nach jedem Bearbeitungsschritt werden alle parallel laufenden Arbeitspakete wieder synchronisiert und das Ergebnis ist eine neue Collection. Dies ist gleichzeitig ein Vorteil und ein Nachteil: - Vorteil, weil ich mit jedem Zwischenschritt wieder ganz normal weiterarbeiten kann, sequenziell oder parallel. - Nachteil, weil diese Synchronisation sowohl die Paralellisierung als auch die Skalierbarkeit auf mehrere Rechner beschr&amp;#xE4;nkt, denn die Arbeitsergebnisse m&amp;#xFC;ssen wieder auf einem Rechner zusammengebracht werden. . Diese Einschr&amp;#xE4;nkung hat Google zur Weiterentwicklung dieses Ansatzes gebracht: Map &amp; Reduce lautet das Stichwort.
  94. Schauen wir uns an, was hier passiert... Nach jedem Bearbeitungsschritt werden alle parallel laufenden Arbeitspakete wieder synchronisiert und das Ergebnis ist eine neue Collection. Dies ist gleichzeitig ein Vorteil und ein Nachteil: - Vorteil, weil ich mit jedem Zwischenschritt wieder ganz normal weiterarbeiten kann, sequenziell oder parallel. - Nachteil, weil diese Synchronisation sowohl die Paralellisierung als auch die Skalierbarkeit auf mehrere Rechner beschr&amp;#xE4;nkt, denn die Arbeitsergebnisse m&amp;#xFC;ssen wieder auf einem Rechner zusammengebracht werden. . Diese Einschr&amp;#xE4;nkung hat Google zur Weiterentwicklung dieses Ansatzes gebracht: Map &amp; Reduce lautet das Stichwort.
  95. Was hier passiert ist &amp;#xE4;hnlich den parallelen Collections, insofern als die einzelnen Operationen auf Datens&amp;#xE4;tzen parallel ablaufen k&amp;#xF6;nnen. Der entscheidende Unterschied ist, dass beliebig viele Map-Schritte hintereinander ablaufen k&amp;#xF6;nnen, ohne dass eine Synchronisation der gesamten Collection erfolgen muss. Erst ganz am Ende - im Reduce-Schritt - wird das Gesamtergebnis ermittelt. Und auch hier kommt Parallelit&amp;#xE4;t zum Einsatz, da immer zwei Werte in einem Schritt zu einem Zwischenergebnis reduziert werden. Bei diesem Ansatz k&amp;#xF6;nnen sowohl die Daten als auch die Rechenthreads auf verteilten Rechnern leben. Nur am Anfang und ganz am Ende ist Kommunikation zwischen den Knoten n&amp;#xF6;tig, um die Ergebnisse zusammenzuf&amp;#xFC;hren. Man kann sich vorstellen, dass dieses Vorgehen f&amp;#xFC;r Aufgaben wie Indizierung und Suche in gro&amp;#xDF;en Datenbest&amp;#xE4;nden sehr gut skaliert.
  96. Was hier passiert ist &amp;#xE4;hnlich den parallelen Collections, insofern als die einzelnen Operationen auf Datens&amp;#xE4;tzen parallel ablaufen k&amp;#xF6;nnen. Der entscheidende Unterschied ist, dass beliebig viele Map-Schritte hintereinander ablaufen k&amp;#xF6;nnen, ohne dass eine Synchronisation der gesamten Collection erfolgen muss. Erst ganz am Ende - im Reduce-Schritt - wird das Gesamtergebnis ermittelt. Und auch hier kommt Parallelit&amp;#xE4;t zum Einsatz, da immer zwei Werte in einem Schritt zu einem Zwischenergebnis reduziert werden. Bei diesem Ansatz k&amp;#xF6;nnen sowohl die Daten als auch die Rechenthreads auf verteilten Rechnern leben. Nur am Anfang und ganz am Ende ist Kommunikation zwischen den Knoten n&amp;#xF6;tig, um die Ergebnisse zusammenzuf&amp;#xFC;hren. Man kann sich vorstellen, dass dieses Vorgehen f&amp;#xFC;r Aufgaben wie Indizierung und Suche in gro&amp;#xDF;en Datenbest&amp;#xE4;nden sehr gut skaliert.
  97. Was hier passiert ist &amp;#xE4;hnlich den parallelen Collections, insofern als die einzelnen Operationen auf Datens&amp;#xE4;tzen parallel ablaufen k&amp;#xF6;nnen. Der entscheidende Unterschied ist, dass beliebig viele Map-Schritte hintereinander ablaufen k&amp;#xF6;nnen, ohne dass eine Synchronisation der gesamten Collection erfolgen muss. Erst ganz am Ende - im Reduce-Schritt - wird das Gesamtergebnis ermittelt. Und auch hier kommt Parallelit&amp;#xE4;t zum Einsatz, da immer zwei Werte in einem Schritt zu einem Zwischenergebnis reduziert werden. Bei diesem Ansatz k&amp;#xF6;nnen sowohl die Daten als auch die Rechenthreads auf verteilten Rechnern leben. Nur am Anfang und ganz am Ende ist Kommunikation zwischen den Knoten n&amp;#xF6;tig, um die Ergebnisse zusammenzuf&amp;#xFC;hren. Man kann sich vorstellen, dass dieses Vorgehen f&amp;#xFC;r Aufgaben wie Indizierung und Suche in gro&amp;#xDF;en Datenbest&amp;#xE4;nden sehr gut skaliert.
  98. Die Implementierung mit gpars sieht der vorangegangenen L&amp;#xF6;sung sehr &amp;#xE4;hnlich, unterscheidet sich aber im Detail. Ich habe hier die Summierung als reduce aufgef&amp;#xFC;hrt. Tats&amp;#xE4;chlich bietet gpars aber bereits eine solche Summierungsmethode von sich aus an. Im Hintergrund werden hier die Parallelen Arrays aus dem jsr166y benutzt.
  99. Google sagt, dass sie das Patent nur erworben haben, um zu verhindern, dass es ein anderer tut. Nicht verteilte Umgebung: Parallele Collections sind ein einfacheres Konzept!
  100. Eine Dataflow-Variable hat nur drei Operatoren: - Erzeugen - Auf das Binden der Variablen warten - Die Variable binden task: Convenience-Methode zum starten eines asynchronen Jobs
  101. Kein Locking, da keine Daten ver&amp;#xE4;ndert werden Determinismus: Wenn ein Deadlock auftritt, dann finde ich ihn auch mit einem nicht-nebenl&amp;#xE4;ufigen Unit Test
  102. Alles bis auf die Ausgabe des Wertes erfolgt nebenl&amp;#xE4;ufig!
  103. Seiteneffekte: IO, print, exceptions. Man wei&amp;#xDF; nicht, wann die Berechnung ausgef&amp;#xFC;hrt wird. Scala Dataflow Concurrency DSL von J. Boner auf github
  104. und vermutlich andere, von denen ich noch nicht geh&amp;#xF6;rt habe.