concurrency 101 shared state. part 1: general concepts 2
TRANSCRIPT
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: Variables may not get updated The compiler may reorder statements that access variables Variables may remain in the cache
Declaring a variable to be volatile tells the compiler not to cache the variable or reorder statements
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
12
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: synchronized (lock) { code } In Java, any object can serve as a lock (monitor)
Java has synchronized instance methods: 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
14
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}
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
17
Check-then-act A Vector is like an ArrayList, but is synchronized (so, thread safe) Hence, the following code looks reasonable:
if (!myVector.contains(someObject)) { // check myVector.add(someObject); // act}
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 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)
Atomically adds the given value to the current value. boolean compareAndSet(int expect, int update)
Atomically sets the value to the given updated value if the current value == the expected value.
int decrementAndGet() Atomically decrements by one the current value.
int getAndAdd(int delta) 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