Concurrency 101

Download Report

Transcript Concurrency 101

Concurrency 101
Shared state
Part 1: General Concepts
2
Best book

Get this book if you ever
need to do concurrent
programming in Java!

Of course, everything in
Java is also available in
Scala
3
Definitions








Parallel processes: Happening simultaneously (hardware term)
Concurrent processes: Happening asynchronously
Asynchronous: Having no defined ordering of events
Thread-safe: Correct when accessed from multiple threads
Stateless: Containing no mutable quantities
Atomic: Happens "all at once" or not at all
Race condition: Result depends on order of access
Locking: Preventing other threads from accessing a piece of data




Reentrant lock: Can access if already have the lock
Deadlock: Neither thread can progress until other one does
Livelock: Threads fail to get out of each others way
Starvation: A thread never gets a chance to run
4
Locking


Locking, done correctly, gives a thread temporary,
exclusive access to a piece of data
A thread requests a lock, or monitor, and blocks (does
not proceed) until it gets it




When it gets the lock, it does its work, and releases the lock
In Java, any object can serve as a lock
Another thread that requests the same lock may have to
wait for it
Extremely important:

There is no necessary connection between the lock and the
data that the thread wants to manipulate
5
When to synchronize

In Java, locking goes by the name synchronization


When two or more threads have access to the same mutable
state, every access, everywhere, must be synchronized. No
exceptions for any reason!
When an invariant involves more than one variable, all such
variables must be protected with the same lock

Synchronization can be extremely expensive
Improper synchronization can lead to race conditions,
deadlock, livelock, starvation, or failed visibility

Immutable values never need to be synchronized

6
Visibility


Proper synchronization ensures that threads always see the most
up-to-date values of mutable variables
Otherwise:




Declaring a variable to be volatile tells the compiler not to
cache the variable or reorder statements


Variables may not get updated
The compiler may reorder statements that access variables
Variables may remain in the cache
This guarantees visibility, but not atomicity
32-bit variables may be stale (hold old values), but they will be
legal values

This guarantee does not hold for 64-bit values (double and long)
7
Other safety measures



Never start a thread from inside a constructor
Volatile variables, if written only from a single thread,
may be read from multiple threads without
synchronization
Some Java-provided data structures are thread-safe;
others are not. Pay attention to which is which.



Even thread-safe data structures can be used in an unsafe way
Local variables are thread-safe; they cannot be shared
ThreadLocal variables are those for which each
thread has a separate copy
8
Object safety


You must understand an object's invariants (what
makes it internally consistent) and postconditions
(how it is allowed to change)
Proper synchronization is easier to guarantee if all
access is via the object's methods
9
Summary (Goetz, page 110)











It’s the mutable state, stupid.
Make fields final unless they need to be mutable.
Immutable objects are automatically thread-safe.
Encapsulation makes it practical to manage the complexity.
Guard each mutable variable with a lock.
Guard all variables in an invariant with the same lock.
Hold locks for the duration of compound actions.
A program that accesses a mutable variable from multiple threads without
synchronization is a broken program.
Don’t rely on clever reasoning about why you don’t need to synchronize.
Include thread safety in the design process - or explicitly document that your
class is not thread-safe.
Document your synchronization policy.
10
Part 2: Tools
11
Creating and starting Threads

There are two ways to create a Thread:

Define a class that extends Thread




Supply a public void run() method
Create an object o of that class
Tell the object to start: o.start();
Define a class that implements Runnable (hence it is free to
extend some other class)




Supply a public void run() method
Create an object o of that class
Create a Thread that “knows” o: Thread t = new Thread(o);
Tell the Thread to start: t.start();
12
Synchronizing Threads in Java

Java has a synchronized statement:



Java has synchronized instance methods:



synchronized (lock) { code }
In Java, any object can serve as a lock (monitor)
synchronized public void myMethod(args) { body }
This is equivalent to
 public void myMethod(args) {
synchronized(this) { statements }
}
Java has synchronized static methods:


public static void myMethod(args) { statements }
They are synchronized on the class object (a built-in object that
represents the class)
13
Synchronizing in Scala

Same concepts, slightly different syntax

A synchronized code block:


myObject.synchronized {
// code block
}
A synchronized method:

def myMethod(args) = synchronized {
// code block
}
14
Thread pools







Since threads are expensive to create, Java provides a way to
recycle threads
import java.util.concurrent.Executors;
ExecutorService pool =
Executors.newFixedThreadPool(poolSize);
Create some Runnable objects (objects that implement
public void run()
exec.execute(Runnable object)
void shutdown() stops accepting new tasks
List<Runnable> shutdownNow() stops all tasks and
returns a list of unfinished tasks
15
Some thread-safe data structures

In java.util:



Hashtable (not HashMap)
Vector
In java.util.concurrent:




ConcurrentHashMap
ArrayBlockingQueue
ConcurrentLinkedQueue
FutureTask
16
Check-then-act


A Vector is like an ArrayList, but is synchronized (so, thread safe)
Hence, the following code looks reasonable:


But there is a “gap” between checking the Vector and adding to it



During this gap, some other Thread may have added the object to the array
Check-then-act code, as in this example, is unsafe
You must ensure that no other Thread executes during the gap


if (!myVector.contains(someObject)) { // check
myVector.add(someObject);
// act
}
synchronized(myVector) {
if (!myVector.contains(someObject)) {
myVector.add(someObject);
}
}
So, what good is it that Vector is synchronized?

It means that each call to a Vector operation is atomic
17
java.util.concurrent.atomic


The java.util.concurrent.atomic package provides classes and
methods to help avoid the check-then-act problem
For example, here are some methods in AtomicInteger:

int addAndGet(int delta)


boolean compareAndSet(int expect, int update)


Atomically decrements by one the current value.
int getAndAdd(int delta)


Atomically sets the value to the given updated value if the current value
== the expected value.
int decrementAndGet()


Atomically adds the given value to the current value.
Atomically adds the given value to the current value.
boolean weakCompareAndSet(int expect, int update)

Atomically sets the value to the given updated value if the current value
== the expected value.
18
The End
19