Concurrency On The JVM

Download as pdf or txt
Download as pdf or txt
You are on page 1of 45

Concurrency on the JVM

Executors
Fork / Join Framework
Actors

Th Letschert, THM

Concurrency on the JVM


What is concurrency
Things get done concurrently or in parallel
Concurrent vs parallel
The same, only different
Concurrent
doing several things at the same time is motivated by the application
(make it more responsive)
Parallel
doing things things at the same time is motivated by efficiency
(make it run faster)

What is the JVM


Java Virtual Machine
used to be execution environment for Java programs
now is the host of many JVM-based languages
Groovy, JPython, Scala, Clojure, ...

Seite 2

Concurrency Power and Perils


Power
Responsiveness
The system may listen attentively to several partners at the same time

Speed
Exploit the power of working in parallel: distribution, multi-cores, DMAs, ...

Perils
Development of concurrent app. is more demanding

Deadlocks
Starvation
Race Conditions
Non-deterministic behavior
...

Seite 3

Basic Concurrency Features: Threads and their Synchronization


Thread
Define

Sub-class Thread / provide Runnable to constructor of Thread

Create

Create object

Start

Call start-Method

Stop

OOPS, How do I do that ?

Mutual Exclusion
synchronized

method

synchronized

block

Conditional Synchronization
wait

method of class object

notify

notifyAll

Features are tailored to fit with


the Monitor-Concept
Seite 4

Basic Concurrency Concept: Monitor


Monitor
Origin: Tony Hoare / Per Brinch Hansen (Edsger Dijkstra)
Concept for System Programming
Invented in the 1960s / 1970s

Basic Ideas
Monitor = Class + Synchronization
Localize resources and their associated methods and synchronization in one entity
Synchronization
Solve race conditions
Solve conditional synchronization

Mutual Exclusion
Race conditions are solved by mutual exclusion

Conditional Synchronization
Preconditions: Some operations can be performed only if a condition holds
wait / notify:

System has to provide means for threads / processes


to wait for and signal the fulfillment

Seite 5

Basic Concurrency Concept: Monitor


Monitor Example
class PlaceMonitor {

Conceptual

Token place;
boolean empty = true;
// one call must not interfere with other calls
// Precondition: empty
void put(Token t) {
place = t;
empty = false;
}
// one call must not interfere with other calls
// Pre: not empty
class PlaceMonitor {
Token get() {
empty = true;
Token place;
return place;
boolean empty = true;
}
synchronized void put(Token t) throws InterruptedException {
while ( ! empty ) wait();
place = t;
empty = false;
notify();
}

synchronized Token get() throws InterruptedException {


while ( empty ) wait();
empty = true;
notify();
return place;
}

Realized with Java's monitor features

}
Seite 6

Basic Concurrency Concept: Monitor


Monitor Example
public static void main(String[] args) {
final PlaceMonitor pm = new PlaceMonitor();
new Thread(new Runnable(){
public void run() {
while (true) {
try {
pm.put(Token.PING);
Thread.sleep(1000);
System.out.println(pm.get());
} catch (InterruptedException e) {}
}
}
}).start();
new Thread(new Runnable(){
public void run() {
while (true) {
try {
pm.put(Token.PONG);
Thread.sleep(1000);
System.out.println(pm.get());
} catch (InterruptedException e) {}
}
}
}).start();

Two threads using the monitor

Seite 7

Basic Concurrency Concept: Monitor


Monitor
Anything wrong with the monitor and thus Java's basic concurrency features ?
No !
But

It wasn't meant to be used for solving computational problems


on modern multi-core systems!

Seite 8

Concurrency and Applications on Multi-Core Systems

How to benefit from multi-cores


Tasks
divide your task into (mostly) independent tasks.
Cores
create about as many threads as there are available cores.
I/O
For I/O-intensive tasks spend some extra threads
Threads
Number of threads = Number of cores / (1 blocking coefficient)
blocking coefficient = 0 for computational intensive tasks
increases with I/O operations should not reach 1

Seite 9

Concurrency and Applications on Multi-Core Systems

How to benefit from multi-cores


More Abstraction: Separate tasks and threads!
Tasks
That's what application programmers should have in mind
Threads
Thread-management is an implementation feature
Separate threads and tasks
Tasks depend on the problem
Threads depend mostly on the machine (which may vary)
Map the machineindependent
solution to a
certain
configuration

Task 2
Task 4
Task 1
Task 3

threads

Task 5

Machine

Problem
Seite 10

Higher Level Concurrency: the Executor Framework

Executor Framework: Executors


Separates tasks and threads
by providing an additional abstraction layer.
Introduced with Java 1.5 (in package java.util.concurrent)
Provides scalability
Eases thread management
Provides several kinds of manageable executors for computational tasks
Task 2
Task 4
Task 1
Task 3

Task 5

Executor

threads

Seite 11

Higher Level Concurrency: the Executor Framework

Simple Executor Example: A UDP-Server


import
import
import
import
import

Request_Tasks

the net

java.io.IOException;
java.net.DatagramPacket;
java.net.DatagramSocket;
java.util.concurrent.Executor;
java.util.concurrent.Executors;

public class UDPServer {


private static int nThreads = Runtime.getRuntime().availableProcessors()*2;

private static Executor executor = Executors.newFixedThreadPool(nThreads);

Executor for the tasks: Here a fixed


size pool of threads. The size depends
on the actual machine.

public void serve(int port) {


try {
DatagramSocket dtgrmSocket = new DatagramSocket(port);
byte[] buf = new byte[256];
DatagramPacket rcvpkt = new DatagramPacket(buf, buf.length);
while (true) {
try {

Tasks are determined by messages


received.

dtgrmSocket.receive(rcvpkt);
Request_Task rqT = new Request_Task(rcvpkt);

Create Task and hand it over to the


executor.

executor.execute(rqT);
} catch (IOException e) {}
}
} catch (IOException e) {}
}

public class Request_Task implements Runnable {


@Override
public void run() { }

Tasks are just Runnable

}
Seite 12

Higher Level Concurrency: the Executor Framework

Executor Framework: Futures


Futures are structured callbacks
Introduced with Java 1.5 as part of the executor framework
Provide access to (future) results produced by other threads

client
thread

Task

Immediate response

Future
Future result =
synchronized place where
to put (executor) and take
(client) the result of the
computation.

worker
threads

Seite 13

Executor: Does the work

Higher Level Concurrency: the Executor Framework

Executor Framework: Future Example


import
import
import
import
import
import
import

java.util.LinkedList;
java.util.List;
java.util.concurrent.Callable;
java.util.concurrent.ExecutionException;
java.util.concurrent.ExecutorService;
java.util.concurrent.Executors;
java.util.concurrent.Future;

public class FutureEx {


static int nThreads = Runtime.getRuntime().availableProcessors();
static ExecutorService executor = Executors.newFixedThreadPool(nThreads);
static int someExpensiveOperation(int arg) throws InterruptedException { Thread.sleep(5000); return arg+1; }
public static void main(String[] args) throws InterruptedException, ExecutionException {
int x = 0;
List<Future<Integer>> fl = new LinkedList<>();
while (true) {
final int xx = x;

fl.add(
executor.submit(
new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return someExpensiveOperation(xx);
}
}));

x++;
if (x == 10) break;

for (Future<Integer> fi : fl) {


System.out.println(fi.get());
}
}
}
Seite 14

Higher Level Concurrency: the Executor Framework

Executor Framework: Synchronized Collections and Synchronziers


provide standardized solutions to synchronization problems

Concurrent Collections
ConcurrentHashMap
CopyOnWriteArrayList
CopyOnWriteArraySet
ConcurrentLinkedQueue
ArrayBlockingQueue

Synchronizers

Lock
Condition
Queue / Deque
CyclicBarrier
CountDownLatch
Semaphore
Exchanger

explicit Mutex
explicit Condition variable
Queues used as synchronizer
Barrier
Latch

LinkedBlockingQueue
SynchronousQueue
PrioritiyQueue
DelayQueue
ConcurrentLinkedQueue

Given the difficulty of using wait and notify correctly, you


should use the higher-level concurrency utilities instead.
J.Bloch, Effective Java 2 nd edition
Seite 15

More Granular Concurrency: the Fork-Join Framework

Fork-Join Framework
Separates tasks and threads even more
loosening the task thread relation
Introduced with Java 1.7 (in package java.util.concurrent)
threads may be utilized by several computational tasks

Task
Task
Task

Task Task Task

Task

Task

Task
Task

Task

Task

Task

Task

Task

Executor
A thread works on its task
until it is completed, then it
takes the next one.
Number of tasks being processed at one time
= number of threads.
A blocking operation blocks the thread!
Blocking while waiting for anther task in the
pool may lead to deadlock.

ForkJoinPool
Number of tasks being processed at one time
may be larger number of threads.

Seite 16

More Granular Concurrency: the Fork-Join Framework

Fork-Join Framework / Example: Fib with Executor (1)


import
import
import
import
import

java.util.concurrent.Callable;
java.util.concurrent.ExecutionException;
java.util.concurrent.ExecutorService;
java.util.concurrent.Executors;
java.util.concurrent.Future;

public class ExecutorFib {


static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
static int fib(int n) throws InterruptedException, ExecutionException {
Future<Integer> ff = executor.submit(new Fib(n));
return ff.get();
}
static class Fib implements Callable<Integer> {
int arg;
public Fib(int arg) { this.arg = arg; }

@Override
public Integer call() throws Exception {
return (arg == 0 || arg == 1) ? arg : fib(arg-1) + fib(arg-2);
}

public static void main(String[] args) throws InterruptedException, ExecutionException {


System.out.println("Cores: " + Runtime.getRuntime().availableProcessors());
System.out.println(fib(8));
System.exit(0);
}
}
Cores: 8
21

With 8 threads, the largest number that can be computed, is fib(8)


Seite 17

More Granular Concurrency: the Fork-Join Framework

Fork-Join Framework / Example: Fib with ForkJoinPool (1)


import java.util.*;
import java.util.concurrent.*;
public class ForkJoinFib {
static ForkJoinPool fjPool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());
static int fib(long n) throws InterruptedException, ExecutionException {
return new Fib(n).call();
}
static class Fib implements Callable<Integer> {
long arg;
public Fib(long arg) { this.arg = arg; }
@Override
public Integer call() {
if (arg == 0 || arg == 1) return arg;
else {

Callable<Long>[] fibs = (Callable<Long>[]) new Callable[]{


new Fib(arg-1),
new Fib(arg-2)};
List<Future<Long>> ff = fjPool.invokeAll(Arrays.asList(fibs));
try {
return ff.get(0).get() + ff.get(1).get();
} catch (InterruptedException | ExecutionException e) { return -1; }
}

}
public static void main(String[] args) throws InterruptedException, ExecutionException {
System.out.println("Cores: " + Runtime.getRuntime().availableProcessors());
long start = System.currentTimeMillis();
System.out.println(fib(49));
long stop = System.currentTimeMillis();
System.out.println("duration (ms): " + (end-start));
System.exit(0);
With 8 threads fib(49) can
}
Seite 18
}

Cores: 8
7778742049
duration (ms): 451472

be computed in reasonable time

More Granular Concurrency: the Fork-Join Framework

Fork-Join Framework / Example: Determinate


private Matrix coMatrix(int x, int y) {
Matrix res = new Matrix(lineCount-1, columnCount-1);
for ( int i=0; i<lineCount-1; ++i)
for ( int j=0; j<columnCount-1; ++j)
res.m[i][j] = m [i<x?i:(i+1)] [j<y?j:(j+1)];
return res;
}

class Matrix {
double [][] m = null;
int lineCount;
int columnCount;
. . .

double determinate() throws


IllegalStateException {
if (lineCount != columnCount)
throw new IllegalStateException("Matrix not square");
if (lineCount == 1)
return m[0][0];
else {
double res = 0.0;
int f = -1;
for (int i = 0; i < lineCount; ++i) {
f = f * -1;
res = f * m[i][0] * (coMatrix(i, 0)).determinate() + res;
}
return res;
}

Recursive computation of
determinate via
determinant of co-matrix

. . .
}

11
21
31
41

12
22
32
42

13
23
33
44

14
24
34
45

22 23 24
11 * 32 33 34 - 21 *
42 44 45

12 13 14
32 33 34 + 31 *
42 44 45

Seite 19

12 13 14
12 13 14
41
*
22 23 24
22 23 24
32 33 34
42 44 45

More Granular Concurrency: the Fork-Join Framework

Fork-Join Framework / Example: Determinate with Fork-Join


double determinate() throws IllegalStateException, InterruptedException, ExecutionException {
if (lineCount != columnCount)
throw new IllegalStateException("Matrix not square");
if (lineCount == 1)
return m[0][0];
else {
@SuppressWarnings("unchecked")
Callable<Double>[] subDC= (Callable<Double>[]) new Callable[lineCount];
for (int i = 0; i < lineCount; ++i) {
final int fi = i;
subDC[i] = new Callable<Double>() {
@Override
public Double call() throws Exception {
return (coMatrix(fi, 0)).determinate();
}
};
}
List<Future<Double>> suDF = fjPool.invokeAll(Arrays.asList(subDC));
double res = 0.0;
int f = -1;
for (int i = 0; i < lineCount; ++i) {
f = f * -1;
res = f * m[i][0] * suDF.get(i).get() + res;
}
return res;
}

Ax = b
xi = |Abi | / |A|

12x12 System,
sequential solution :
2'072'903 ms
ForkJoin determinant computation:
725'546 ms
On Intel Core i7 CPU 860 @ 2.80GHz, (8 cores)
Seite 20

Considerable speed-up on
a multi-core system!

More Granular Concurrency: Parallel Collections in Scala

Example: Determinate in Scala


def determinante () : Double = {
if (lineCount == 1) m(0)(0) else {
var res = 0.0
var f = -1;
for (i <- 0 to lineCount-1) {
f = f * -1;
res = f * m(i)(0) * coMatrix(i, 0).determinante() + res;
}
res
}
}
def determinante () : Double = {
if (lineCount == 1) m(0)(0) else {
var res = 0.0
val sd = (0 to lineCount-1).toArray.par.map(coMatrix(_, 0).determinante())
var f = -1;
for (i <- 0 to lineCount-1) {
f = f * -1;
res = f * m(i)(0) * sd(i) + res;
}
res
}
}

Ax = b
xi = |Abi | / |A|

12x12 System
seqential Scala :
parallel Scala :
sequential Java :
ForkJoin Java :

3'045'511 ms
2'854'433 ms
2'072'903 ms
725'546 ms
Seite 21

Sequential Scala solution

Scala solution with


Parallel Collection

Speed-up but not as much


as Fork-Join with Java!

More Granular Concurrency: the Fork-Join Framework

Fork-Join Framework
Use: Parallel programming where problems
can be recursively split into smaller parts,
solved in parallel
and then recombined

Implementation: Specialized thread pool


for paralleling recursive tasks
maps an unbounded number of tasks to a limited number of threads

Seite 22

Actors

Actor
What is an Actor?
Actor: some-one/-thing that acts not necessarily in a play or movie

Actors in computer science


Theory:

atomic entity of concurrent systems

Practice:

linguistic feature of concurrent programming languages

Seite 23

Actors as a theoretical concept

Actor Theory : In Search for the Essence of Concurrent computation


The essence of sequential programs
Scott / Strachey approach of denotational semantics
Each sequential program is some -expression
with a well defined meaning: a (higher-order) function
Meaning of compound expressions depends only on the meaning of subexpressions

The essence of concurrent programs


Search for an appropriate extension of denotational semantics to concurrency failed
Basic questions: What are the atomic parts of a concurrent system?
Two main approaches (besides Petri Nets):
Concurrent systems consist of
processes communicating values through channels
Process Calculi
Tony Hoare (CSP) / Robin Milner (CSS) ...
Concurrent systems consist of
actors sending and receiving actors
Actor Systems
Carl Hewitt / Gul Agha / Irene Greif ...

Seite 24

Influenced by the actor-model, Milner also


developed the -calculus: processes are
values that may be communicated

Actors as a theoretical concept

Actor Model
An actor is an entity that may
send messages (which are actors) to other actors
create new actors
change its behavior designating how to handle
the next message it receives

Main difference to process calculi

No channels
Message transfer is actor to actor
One atomic entity: the actor
The transport medium is intentionally left outside the model :
indeterministic message delivery: a message send will be delivered,
but without any predictable time limit or order

Seite 25

The
completely
unknown
outer space

Actors as a linguistic means

Actors in Use:
In Search for a Convenient Expression of Concurrency
Malady:
Writing programs in a monitor style, ie.
active entities (processes / threads) working on
shared mutable passive resources
causes pain.

Cure
Write programs that avoid shared mutable state.

No shared state
=> Communication solely through message passing
No mutable state
=> Functional style of programming
Carl Hewitt on the actor model - live:
Seite 26
http://ak.channel9.msdn.com/ch9/01ad/2ca509cc-d410-4663-8679-6ece29cd01ad/LangNEXT2012HewittMeijerSzyperkiActors_mid.mp4

Actors as a linguistic means

Actors in Programming Languages (Wikipedia: Actor libs for Java):

ill be
w
a
k
d Ak lowing.
n
a
a
l
Scal n the fo
n
i
i
rs
d
Acto nsidere
co

Seite 27

Actors as a linguistic means


Akka
- library, framework, toolkit
- to support distributed, concurrent systems
- written in Scala
- with Java and Scala API

Akka Actors / Simple Example


import akka.actor.UntypedActor;
public class MyFirstActor extends UntypedActor {

public void onReceive(Object message) throws IllegalArgumentException {


if (message instanceof String) {
System.out.println("Hello " + message);
} else throw new IllegalArgumentException("Unknown message: " + message);
}
}
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;
public class ActorUser {
public static void main(String[] args) throws InterruptedException {
ActorSystem system = ActorSystem.create("MySystem");
ActorRef myActor = system.actorOf(new Props(MyFirstActor.class), "myFirstActor");
myActor.tell("How are you?");
Thread.sleep(1000);
system.shutdown();
}
}

http://akka.io/
Seite 28

Download akka-2.0.1.zip and add all jars within it


including scala-library.jar to the build path
(or use Akka's preferred strange tools like sbt etc.)

Actors as a linguistic means

Actors in Programming Languages


Message queues per actor instance
Global message bus to transfers messages
Considerable differences in details between
different actor implementations
Thread based: Actor ~ thread
Event based: no direct coupling of threads and events
allow for much more actors than threads

Some implementations support remote actors


i.e. actors on different (real or virtual) machines

Seite 29

Actors as a linguistic means / Bounded Buffer Example

Actors / form Passive to Active Monitors


Example Bounded Buffer shared state solution:
Classic example of a monitor (passive shared resource, active users)
Race conditions and conditional synchronization have to solved

Example Bounded Buffer actor solution:


actor solution: active monitor

put
get
producer

buffer

consumer

producer

Buffer as passive monitor

buffer

Buffer as active monitor

Seite 30

consumer

Actors as a linguistic means / Bounded Buffer Example


Bounded Buffer / old fashioned shared state passive Monitor version
public class Buffer {
private int store;
private boolean full = false;
synchronized void put (int v) {
Wait for condition
while (full) {
try { wait(); } catch (InterruptedException e) {}
}
notifyAll();
full = true;
store = v;
}

put
get
producer

buffer

consumer

Wait for condition

synchronized int get () {


while (!full) {
try { wait(); } catch (InterruptedException e) {}
}
notifyAll();
full = false;
return store;
public class Producer extends Thread {
}
private Buffer buffer;

public class Consumer extends Thread {


private Buffer buffer;

public Producer(Buffer buffer) {


this.buffer = buffer;
}

public Consumer(Buffer buffer) {


this.buffer = buffer;
}

public void run () {


int i = 0;
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
buffer.put(i++);
}
}
Seite 31

public void run () {


int i = 0;
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
System.out.println(buffer.get());
}
}

Actors as a linguistic means / Bounded Buffer Example


Bounded Buffer / active, Actor version
Buffer receives messages
does not share state
producer
still has to synchronize producers and consumers
using message queues
instead of process queues (i.e. condition variable)

put

get

OK

value
buffer

consumer

if (message is Put-message) {
if (full) {
delay(message);
} else {
full = true;
store = message.getV();
if (thereIsDelayed Get-message m){
Send answer for m
}
}

synchronized void put (int v) {


while (full) {
wait();
}
notifyAll();
full = true;
store = v;
}

if (message is Get-message) {
if (!full) {
delay(message);
} else {
full = false;
store = message.getV();
if (thereIsDelayed Put-message m){
Send answer for m
}

synchronized int get () {


while (!full) {
wait();
}
notifyAll();
full = false;
return store;
}

Seite 32

pseudo code !

Actors as a linguistic means / Bounded Buffer Example


Bounded Buffer / Active Buffer as Actor (1)
import java.util.Deque;
import java.util.ArrayDeque;
import akka.actor.ActorRef;
import akka.actor.UntypedActor;

get

OK

value
buffer

public class ActiveBuffer extends UntypedActor {


public static class OKMsg {}
public static class GetMsg {}
public static class PutMsg {
private int value;
public PutMsg(int value) { this.value = value;}
public int getValue() { return value; }
}
public static class AnswerMsg {
private int value;
public AnswerMsg(int value) { this.value = value;}
public int getValue() { return value; }
}
private int store;
private boolean full = false;
static class Pair {
Object msg;
ActorRef replyTo;
Pair(Object msg, ActorRef sender) { this.msg = msg;
}

this.replyTo = sender; }

private Deque<Pair> messageQueue = new ArrayDeque<>();


@Override
public void onReceive(Object message) throws Exception {
. . . next slide . . .
}
}

put

Seite 33

Actors as a linguistic means / Bounded Buffer Example


Bounded Buffer / Active Buffer as Actor (2)
@Override
public void onReceive(Object message) throws Exception {
if (message instanceof PutMsg) {
if (full) {
messageQueue.addLast(new Pair(message, getSender()));
} else {
store = ((PutMsg) message).getValue();
full = true;
for (Pair p: messageQueue) {
if (p.msg instanceof GetMsg) {
messageQueue.remove(p);
p.replyTo.tell(new AnswerMsg(store));
full = false;
break;
}
}
}
} else if (message instanceof GetMsg) {
if (!full) {
messageQueue.addLast(new Pair(message, getSender()));
} else {
getSender().tell(new AnswerMsg(store));
full = false;
for (Pair p: messageQueue) {
if (p.msg instanceof GetMsg) {
messageQueue.remove(p);
store = ((PutMsg) p.msg).getValue();
full = true;
break;
}
}
}
} else throw new IllegalArgumentException("Unknown message: " + message);
}

Seite 34

put

get

OK

value
buffer

Actors as a linguistic means / Bounded Buffer Example


Bounded Buffer / Producer

put

import static bounded_buffer_A.ActiveBuffer.PutMsg;


import static bounded_buffer_A.ActiveBuffer.OKMsg;

OK

public class Producer {


static private ActorRef buffer;
static private ActorRef pa;
static volatile boolean stopped = false;

producer

public static class ProduceMsg {}

public static void init(ActorRef buffer, ActorSystem system) {


Producer.buffer = buffer;
pa = system.actorOf(new Props(ProducerActor.class), "Producer");
}
public static void start() {
new Thread(new Runnable () {
public void run () {
while (! stopped) {
pa.tell(new ProduceMsg());
try { Thread.sleep(1000); } catch (InterruptedException e) {}
}
}}).start();
}
public static void stop() {
stopped = true;
}
public static class ProducerActor extends UntypedActor {
private int v = 0;
public void onReceive(Object message) throws IllegalArgumentException {
if (message instanceof ProduceMsg) {
buffer.tell(new PutMsg(v++), getSelf());
} else if (message instanceof OKMsg) {
buffer.tell(new PutMsg(v++), getSelf());
} else throw new IllegalArgumentException("Unknown message: " + message); }
}

Seite 35

Actors as a linguistic means / Bounded Buffer Example


Bounded Buffer / Consumer

get

import static bounded_buffer_A.ActiveBuffer.GetMsg;


import static bounded_buffer_A.ActiveBuffer.AnswerMsg;

value

public class Consumer {


private static ActorRef buffer;
private static ActorRef ca;

consumer

public static class StartMsg {}


public static void init(ActorRef buffer, ActorSystem system) {
Consumer.buffer = buffer;
ca = system.actorOf(new Props(ConsumerActor.class), "Consumer");
}
public static void start() {
ca.tell(new StartMsg());
}

public static class ConsumerActor extends UntypedActor {


public void onReceive(Object message) throws IllegalArgumentException {
if (message instanceof StartMsg) {
buffer.tell(new GetMsg(), getSelf());
} else if (message instanceof AnswerMsg) {
buffer.tell(new GetMsg(), getSelf());
} else throw new IllegalArgumentException("Unknown message: " + message);
}
}

Seite 36

Actors as a linguistic means / Bounded Buffer Example


Bounded Buffer / Put it all together
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;

put

get

OK

value

producer

public class Main {


public static void main(String[] args) throws InterruptedException {
ActorSystem system = ActorSystem.create("MySystem");
ActorRef bufferActor = system.actorOf(new Props(ActiveBuffer.class), "ActiveBuffer");
Producer.init(bufferActor, system);
Consumer.init(bufferActor, system);
Producer.start();
Consumer.start();
Thread.sleep(50000);
Producer.stop();
}

system.shutdown();

Seite 37

buffer

consumer

Actors as a linguistic means

Actors Some observations based on the buffer example


The actor solution is much more complex than the shared state solution
Why:
The synchronization problem has to be solved, it does not just go away
Condition variables (wait / notify) provide functionality
that had to be hand-coded (messageQueue)
Actors are not threads: they need someone that pushes them (threads)
So:
Actors aren't a replacement of threads and shared state
Actors should be used for solving appropriate problems
Buffers (shred state / conditional sync.) are not a convincing example for actors

The example was a little bit unfair, an actor-less active solution


would have to work with socket-communication and would be even
complexer than the actor solution.
Seite 38

Actors as a linguistic means / Conditional Receive

Actors Observations based on the buffer example


The actor code is much more complex than the shared state solution
Why:
The synchronization problem has to be solved, it does not just go away
Condition variables (wait / notify) provide functionality
that had to be hand-coded (messageQueue)

So:

ept
c
c
a
Actors are not threads: they need someone that pushes themr(threads)
ved: s.
o
p
im l other
e
b
o
yt
y al
a
a
l
m
e
d
s
ueue ate now,
q
e
g
Actors aren't a replacement of threads
shared
state
pri
ro
essa aand
p
m
p
f
nt o hat are
e
m
e
Actors should beaused
for
solving
g
s t appropriate problems
M na essage
ly m
Buffers are not o
anconvincing
example for actors

Seite 39

Actors as a linguistic means / Conditional Receive

Actors in Scala provide conditional receive


Scala : A JVM-based language with Actors
import scala.actors.Actor

Active buffer

case class PutMsg(x: Int)


case class GetMsg

producer and consumer

class ActiveBuffer extends Actor {


import scala.actors.Actor._

var full : Boolean = false


var data: Int = 0

object Main extends App {


override def main(args : Array[String]) : Unit = {
val buffer = new ActiveBuffer
buffer.start

def act = {
loop {
react {
case PutMsg(x) if ! full =>
data = x;
full = true;
reply()

val producer = actor {


var x = 1
while (true) {
buffer !? PutMsg(x)
x = x+1
}
}

case GetMsg if full =>


full = false;
reply(data)

val consumer = actor {


while (true) {
Thread.sleep(1000)
val v = (buffer !? GetMsg)
println("GET " + v )
}
}

}
}
}
}
}
}

Seite 40

Actors as a linguistic means / Scala vs Akka

Actors Observations based on the


Scala version of buffer example

ile
conc
e
r
ss to tors
e
r
g
o
c
in pr kka's a
k
r
o
A
is w 's and
e
r
e
a
Th
S cal

The code is much less complex than the Java / Akka solution
Akka version

Why:
Scala is much less verbose than Java
Condition variables (wait / notify) provide functionality
that has been transferred to conditional receive statements

Crafted in the Akka version


many lines of code

A reply statement can be used


Actors in Scala may be
Purely reactive (buffer) or
Thread-driven (producer, consumer)

Actors are reactive, threads may


send, the sender can only be
identified in an actor if it is an actor.
So the producer is thread + actor.

loop {
react {
case PutMsg(x) if ! full =>
data = x;
full = true;
reply()
case GetMsg if full =>
full = false;
reply(data)
}
}

Reactive with
conditional receive

Seite 41

Thread-driven

val producer = actor {


var x = 1
while (true) {
buffer !? PutMsg(x)
x = x+1
}
}

Actors as a linguistic means / Thread Reuse


Result: 832040, duration: 597

Reactive vs Thread-based Actors in Scala

Fork/Join Solution

import scala.actors.Actor

import scala.actors.Actor

class F extends Actor {


def act = {
loop {
react {
case x : Long =>
if (x < 2) {
reply(x)
} else {
val f1 = new F()
val f2 = new F()
f1.start
f2.start
val v1 : Long = (f1 !? x-1).asInstanceOf[Long]
val v2 : Long = (f2 !? x-2).asInstanceOf[Long]
reply (v1+v2)
}
}
}
}
}

class F extends Actor {


def act = {
loop {
react {
case x : Long =>
if (x < 2) {
reply(x)
} else {
val f1 = new F()
val f2 = new F()
f1.start
f2.start
val v1 : Long = (f1 !? x-1).asInstanceOf[Long]
val v2 : Long = (f2 !? x-2).asInstanceOf[Long]
reply (v1+v2)
}
}
}
}
}

object Fib extends App {


override def main(args : Array[String]) : Unit = {
val fib = new F()
fib.start
val start = System.currentTimeMillis();
val fibR = fib !? 30L
val stop = System.currentTimeMillis();
println("result: "+fibR+", duration: "+(stop-start))
System.exit(0);
}
}
Result: 832040, duration: 48674

object Fib extends App {


override def main(args : Array[String]) : Unit = {
val fib = new F()
fib.start
val start = System.currentTimeMillis();
val fibR = fib !? 30L
val stop = System.currentTimeMillis();
println("result: "+fibR+", duration: "+(stop-start))
System.exit(0);
}
}
. . . still computing . . .

Reactive Scala actor, Fibonacci(30) can be computed, but is


rather slow compared to a Java-7 fork-join solution
Seite 42

Thread based Scala actor, Fibonacci(30) can not be computed.

Actors as a linguistic means

Actors : Message Passing Stile + Reactive Stile ( + Thread Reuse )


Message passing style:
Processes / threads communicate solely through messages: No shared state.
Avoids problems of shared state there is no shared state
Synchronization is moved but not removed .

Reactive style
Actions are defined not by control-statements (eventually with blocking actions), but
through their reaction on events (reception of messages).
Active and reactive components may be both part
of an application. It depends on the application to which extend.
Purely reactive actions can be implemented with high efficiency
(Thousands of them without degenerating performance)
via decoupling threads and independent actions

Reactive, Message-Passing and Thread Reuse is not the same


The message passing style does not necessarily entail a reactive style
A reactive stile entails message passing
Reactive stile allows for thread reuse
Thread reuse is possible without actors (fork/join-framework)
Seite 43

Summary

Concurrency on the JVM


Concepts:
Use standard synchronizers instead of hand crafted solutions
Separate tasks and threads: Executor framework
Allow for thread reuse: Fork/Join framework
Message passing instead of shared state
...

Linguistic features
parallel collections (e.g. Scala)
Actors (e.g. Akka, Scala)
...

New concurrency features: No silver bullet


but progress on:
- How to write correct concurrent distributed programs
- How to get all cores to work
Seite 44

Concurrency on the JVM


Thank you!

Sources
Excutor-Framework / Synchronizers:
B. Goetz, J. Bloch, J. Bowbeer, D. Lea, D. Holmes, T. Peierls:
Java Concurrency in Practice
Addison-Wesley, 2006 (http://jcip.net/)

Fork/Join-Framework
Doug Lea
A Java Fork/Join Framework
http://gee.cs.oswego.edu/dl/papers/fj.pdf

Actors / Theory
Hewitt, Meijer and Szyperski:
The Actor Model (everything you wanted to know, but were afraid to ask)
http://channel9.msdn.com/Shows/Going+Deep/Hewitt-Meijer-and-Szyperski-The-Actor-Model-everything-you-wanted-to-know-but-were-afraid-to-ask

Scala
Homepage : http://www.scala-lang.org/

Akka
Homepage : http://akka.io/
Seite 45

You might also like