0% found this document useful (0 votes)
1 views

Concurrency 1 Bloch

Uploaded by

mcknucklecuck
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
1 views

Concurrency 1 Bloch

Uploaded by

mcknucklecuck
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 34

Principles of Software Construction:

Objects, Design, and Concurrency

Part 3: Concurrency

Introduction to concurrency

Josh Bloch Charlie Garrod

17-214 1
Administrivia

• Homework 5 team sign-up deadline tomorrow at 5 p.m. EDT


• Midterm exam tomorrow/Thursday (4/7-8)
– Practice exam is out
– Exam review session tonight 7-9 p.m.
– Exam released Wednesday night, due Thursday 11:59 p.m.

17-214 2
Today’s lecture: concurrency motivation and primitives

• Why concurrency?
– Motivation, goals, problems, …
• Concurrency primitives in Java
• Coming soon (not today):
– Higher-level abstractions for concurrency
– Program structure for concurrency
– Frameworks for concurrent computation

17-214 6
Moore’s Law (1965) – number of transistors on a chip
doubles every two years

17-214 7
CPU Performance and Power Consumption

• Dennard Scaling (1974) – each time you double transistor density:


– Speed (frequency) goes up by about 40% (Why 40%?)…
– While power consumption of the chip stays constant (proportional to area)
• Combined w/ Moore’s law, every 4 years the number of transistors
quadruples, speed doubles, and power consumption stays constant
• It was great while it lasted…
– Started breaking down in the mid ’90s and broke down completely in the
mid ’90s due to leakage currents ☹️
– More power is required at higher frequency, generating more heat
– And there’s a limit to how much heat a chip can tolerate

17-214 8
One option: fix the symptom

• Dissipate the heat

17-214 9
One option: fix the symptom

• Better(?): Dissipate the heat with liquid nitrogen

17-214 10
17-214 11
Concurrency then and now

• In the past, multi-threading just a convenient abstraction


– GUI design: event dispatch thread
– Server design: isolate each client's work
– Workflow design: isolate producers and consumers
• Now: required for scalability and performance

17-214 12
We are all concurrent programmers

• Java is inherently multithreaded


• To utilize modern processors, we must write multithreaded code
• Good news: a lot of it is written for you
– Excellent libraries exist (e.g., java.util.concurrent)
• Bad news: you still must understand fundamentals
– …to use libraries effectively
– …to debug programs that make use of them

17-214 13
Aside: Concurrency vs. parallelism, visualized

• Concurrency without parallelism:

Thread1
Thread2
Thread3

• Concurrency with parallelism:


Thread1
Thread2
Thread3

17-214 14
Basic concurrency in Java
Review

• An interface representing a task


public interface Runnable {
void run();
}

• A class to execute a task in a CPU thread


public class Thread {
public Thread(Runnable task);
public void start();
public void join();

}

17-214 15
Example: Money-grab (1/2)

public class BankAccount {


private long balance;
public BankAccount(long balance) {
this.balance = balance;
}

static void transferFrom(BankAccount source,


BankAccount dest, long amount) {
source.balance -= amount;
dest.balance += amount;
}

public long balance() {


return balance;
}
}

17-214 16
Example: Money-grab (2/2)
What would you expect this program to print?

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


BankAccount bugs = new BankAccount(100);
BankAccount daffy = new BankAccount(100);

Thread bugsThread = new Thread(()-> {


for (int i = 0; i < 1_000_000; i++)
transferFrom(daffy, bugs, 100);
});

Thread daffyThread = new Thread(()-> {


for (int i = 0; i < 1_000_000; i++)
transferFrom(bugs, daffy, 100);
});

bugsThread.start(); daffyThread.start();
bugsThread.join(); daffyThread.join();
System.out.println(bugs.balance() + daffy.balance());
}

17-214 17
What went wrong?

• Daffy & Bugs threads had a race condition for shared data
– Transfers did not happen in sequence
• Reads and writes interleaved randomly
– Random results ensued

17-214 18
The challenge of concurrency control

• Not enough concurrency control: safety failure


– Incorrect computation
• Too much concurrency control: liveness failure
– Possibly no computation at all (deadlock or livelock)

17-214 19
Shared mutable state requires concurrency control

• Three basic choices:


1. Don't share: isolate state in individual threads
2. Don't mutate: share only immutable state
3. If you must share mutable state: synchronize to achieve safety

17-214 20
An easy fix for our BankAccount program:

public class BankAccount {


private long balance;
public BankAccount(long balance) {
this.balance = balance;
}
static synchronized void transferFrom(BankAccount source,
BankAccount dest, long amount) {
source.balance -= amount;
dest.balance += amount;
}
public long balance() {
return balance;
}
}

17-214 21
Concurrency control with Java’s intrinsic locks
with an explicit lock

• synchronized (lock) { … }
– Synchronizes entire block on object lock; cannot forget to unlock
– Intrinsic locks are exclusive: One thread at a time holds the lock
– Intrinsic locks are reentrant: A thread can repeatedly get same lock

Thread1
Thread2
Thread3

17-214 22
Concurrency control with Java’s intrinsic locks
with an implicit lock

• synchronized (lock) { … }
– Synchronizes entire block on object lock; cannot forget to unlock
– Intrinsic locks are exclusive: One thread at a time holds the lock
– Intrinsic locks are reentrant: A thread can repeatedly get same lock
• synchronized on an instance method
– Equivalent to synchronized (this) { … } for entire method
• synchronized on a static method in class Foo
– Equivalent to synchronized (Foo.class) { … } for entire method

17-214 23
Another example: serial number generation
What would you expect this program to print?

public class SerialNumber {


private static long nextSerialNumber = 0;
public static long generateSerialNumber() {
return nextSerialNumber++;
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[5];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1_000_000; j++)
generateSerialNumber();
});
threads[i].start();
}
for(Thread thread : threads)
thread.join();
System.out.println(generateSerialNumber());
}
}

17-214 24
What went wrong?

• An action is atomic if it is indivisible


– Effectively, it happens all at once
• No effects of the action are visible until it is complete
• No other actions have an effect during the action
• Java’s ++ (increment) operator is not atomic!
– It reads a field, increments value, and writes it back
• If multiple calls to generateSerialNumber see the same
value, they generate duplicates

17-214 25
Again, the fix is easy

public class SerialNumber {


private static long nextSerialNumber = 0;
public static synchronized long generateSerialNumber() {
return nextSerialNumber++;
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[5];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1_000_000; j++)
generateSerialNumber();
});
threads[i].start();
}
for(Thread thread : threads)
thread.join();
System.out.println(generateSerialNumber());
}
}

17-214 26
But you can do better!
java.util.concurrent is your friend

public class SerialNumber {


private static final AtomicLong nextSerialNumber = new AtomicLong();
public static long generateSerialNumber() {
return nextSerialNumber.getAndIncrement();
}

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


Thread[] threads = new Thread[5];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1_000_000; j++)
generateSerialNumber();
});
threads[i].start();
}
for(Thread thread : threads) thread.join();
System.out.println(generateSerialNumber());
}
}

17-214 27
Some actions are atomic
Precondition: Thread A: Thread B:
int i = 7; i = 42; ans = i;

• What are the possible values for ans?

17-214 28
Some actions are atomic
Precondition: Thread A: Thread B:
int i = 7; i = 42; ans = i;
• What are the possible values for ans?

i: 00000…00000111


i: 00000…00101010

ans: 00000…00101111

17-214 29
Some actions are atomic
Precondition: Thread A: Thread B:
int i = 7; i = 42; ans = i;
• What are the possible values for ans?

i: 00000…00000111


i: 00000…00101010

• In Java:
– Reading an int variable is atomic
– Writing an int variable is atomic

– Thankfully, ans: 00000…00101111 is not possible

17-214 30
Bad news: some simple actions are not atomic

• Consider a single 64-bit long value

high 32 bits low 32 bits


– Concurrently:
• Thread A writing high and low bits
• Thread B reading high and low bits
Precondition: Thread A: Thread B:
long i = 10_000_000_000; i = 42; ans = i;

ans: 01001…00000000 (10,000,000,000)

All are
ans: 00000…00101010 (42) possible!

ans: 01001…00101010 (10,000,000,042)

17-214 31
Yet another example: cooperative thread termination
How long would you expect this program to run?

public class StopThread {


private static boolean stopRequested;

public static void main(String[] args) throws Exception {


Thread backgroundThread = new Thread(() -> {
while (!stopRequested)
/* Do something */ ;
});
backgroundThread.start();

TimeUnit.SECONDS.sleep(5);
stopRequested = true;
}
}

17-214 32
What went wrong?

• In the absence of synchronization, there is no guarantee as to


when, if ever, one thread will see changes made by another
• JVMs can and do perform this optimization (“hoisting”):
while (!done)
/* do something */ ;
becomes:
if (!done)
while (true)
/* do something */ ;

17-214 33
Why is synchronization required for communication
among threads?
• Naively: Process
– Thread state shared in memory Thread Thread

Memory
• A (slightly) more accurate view:
– Separate state stored in registers and caches, even if shared

Process
Thread Thread
Cache Cache

Memory
17-214 34
How do you fix it?

public class StopThread {


private static boolean stopRequested;
private static synchronized void requestStop() {
stopRequested = true;
}
private static synchronized boolean stopRequested() {
return stopRequested;
}
public static void main(String[] args) throws Exception {
Thread backgroundThread = new Thread(() -> {
while (!stopRequested())
/* Do something */ ;
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(5);
requestStop();
}
}
17-214 35
A better(?) solution
volatile is synchronization without mutual exclusion

public class StopThread {


private static volatile boolean stopRequested;

public static void main(String[] args) throws Exception {


Thread backgroundThread = new Thread(() -> {
while (!stopRequested)
/* Do something */ ;
});
backgroundThread.start();

TimeUnit.SECONDS.sleep(5);
stopRequested = true;
}
}

17-214 36
Summary

• Like it or not, you’re a concurrent programmer


• Ideally, avoid shared mutable state
– If you can’t avoid it, synchronize properly
• Some things that look atomic aren’t (e.g., val++)
• Even atomic operations require synchronization
– e.g., stopRequested = true
– Synchronization is required for communication as well as mutual exclusion

17-214 37

You might also like