Synchronization (part 2) EEL 6897 1

advertisement
Synchronization
(part 2)
EEL 6897 1
Acknowledgements
• All the lecture slides were adopted from the slides of
Andy Wellings
EEL 6897 2
Communication and Synchronization
Lecture Aims
• To reinforce synchronized methods and statements
by considering the readers/writers problem
• To introduce the Java 1.5 concurrency utilities
• To consider synchronization and the Java memory
model
• To consider asynchronous thread control
EEL 6897 3
Readers/Writers Problem I
• Many reader threads and many writer threads are attempting to
access an object encapsulating a large data structure
• Readers can read concurrently, as they do not alter the data
• Writers require mutual exclusion over the data both from other
writers and from readers
• There are different variations on this scheme; the one
considered here is where priority is always given to waiting
writers
• Hence, as soon as a writer is available, all new readers will be
blocked until all writers have finished
• Of course, in extreme situations this may lead to starvation of
readers
EEL 6897 4
Readers/Writers Problem II
• Standard solution requires four monitor methods, which are
used:
startRead();
// call object to read data structure
stopRead();
startWrite();
// call object to write data structure
stopWrite();
EEL 6897 5
Readers-Writers Problem III
• Standard solution in monitors is to have two
condition variables: OkToRead and OkToWrite
• This cannot be directly expressed using a
single Java monitor
public class ReadersWriters {
private int readers = 0;
private int waitingWriters = 0;
private boolean writing = false;
EEL 6897 6
Readers-Writers Problem IV
public synchronized void StartWrite()
throws InterruptedException {
while(readers > 0 || writing) {
waitingWriters++;
wait();
waitingWriters--;
}
writing = true;
}
loop to
re-test
the
condition
public synchronized void StopWrite() {
writing = false;
notifyAll();
Wakeup
}
everyone
EEL 6897 7
Readers-Writers Problem V
public synchronized void StartRead()
throws InterruptedException {
while(writing || waitingWriters > 0) wait();
readers++;
}
public synchronized void StopRead() {
readers--;
if(readers == 0) notifyAll();
}
}
EEL 6897 8
Readers-Writers Problem VI
• On awaking after the wait request, the thread must reevaluate the conditions under which it can proceed
• Although this approach will allow multiple readers or
a single writer, arguably it is inefficient, as all threads
are woken up every time the data becomes available
• Many of these threads, when they finally gain access
to the monitor, will find that they still cannot continue
and, therefore, will have to wait again.
• It should also be noted that this solution is not
tolerant to the InterruptedException being thrown
(how would you make it tolerant?)
EEL 6897 9
Java 1.5 Concurrency Utilities
• Java 1.5 has comprehensive support for generalpurpose concurrent programming
• The support is partitioned into three packages:
– java.util.concurrent - this provides various classes to support
common concurrent programming paradigms, e.g., support for
various queuing policies such as bounded buffers, sets and maps,
thread pools etc
– java.util.concurrent.atomic - this provides support for lockfree thread-safe programming on simple variables such as atomic
integers, atomic booleans, etc.
– java.util.concurrent.locks - this provides a framework for
various locking algorithms that augment the Java language
mechanisms, e.g., read -write locks and condition variables.
EEL 6897 10
Locks I
package java.util.concurrent.locks;
public interface Lock {
public void lock();
// Wait for the lock to be acquired.
public Condition newCondition();
// Create a new condition variable for
// use with the Lock.
public void unlock();
…
}
EEL 6897 11
Locks II
package java.util.concurrent.locks;
public interface Condition {
public void await()
throws InterruptedException;
// Atomically releases the associated lock
// and causes the current thread to wait.
public void signal();
// Wake up one waiting thread.
public void signalAll();
// Wake up all waiting threads.
…
}
EEL 6897 12
Lock III
package java.util.concurrent.locks;
public class ReentrantLock implements Lock {
public ReentrantLock();
...
public void lock();
public Condition newCondition();
// Create a new condition variable and
// associated it with this lock object.
public void unlock();
}
EEL 6897 13
Generic Bounded Buffer I
import java.util.concurrent.*;
public class BoundedBuffer<Data> {
private
private
private
private
private
private
private
Data buffer[];
int first;
int last;
int numberInBuffer;
int size;
Lock lock = new ReentrantLock();
final Condition notFull =
lock.newCondition();
private final Condition notEmpty =
Lock.newCondition();
EEL 6897 14
Generic Bounded Buffer II
public BoundedBuffer(int length) {
size = length;
buffer = (Data[]) new Object[size];
last = 0;
first = 0;
numberInBuffer = 0;
}
EEL 6897 15
Generic Bounded Buffer III
public void put(Data item)
throws InterruptedException {
lock.lock();
try {
while (numberInBuffer == size)
notFull.await();
last = (last + 1) % size;
numberInBuffer++;
buffer[last] = item;
notEmpty.signal();
} finally {
lock.unlock();
}
}
EEL 6897 16
Generic Bounded Buffer IV
public Data get()
throws InterruptedException {
lock.lock();
try {
while (numberInBuffer == 0)
notEmpty.await();
first = (first + 1) % size ;
numberInBuffer--;
notFull.signal();
return buffer[first];
} finally {
lock.unlock();
}
}
}
EEL 6897 17
The Java Memory Model (JMM)
• Java has a complex memory model which gives implementers
considerable freedom
• As long as programmers ensures that all shared variables are
only accessed by threads when they hold an appropriate
monitor lock, they need not be concerned with issues such as
multiprocessor implementations, compiler optimisations,
whether processors execute instructions out-of-order etc
• However, synchronization can be expensive, and there are times
when a programmer might want to use shared variables without
an associated monitor lock
• One example is the so-called double-checked locking idiom
• In this idiom, a singleton resource is to be created; this resource
may or may not be used during a particular execution of the
program
• Furthermore, creating the resource is an expensive operation
and should be deferred until it is required
EEL 6897 18
Intuitive Implementation of Resource
public class ResourceController {
private static Resource resource = null;
public static synchronized Resource getResource() {
if(resource == null) resource = new Resource();
return resource;
}
}
• The problem with this solution is that a lock is
required on every access to the resource
• In fact, it is only necessary to synchronize on creation
of the resource, as the resource will provide its own
synchronization when the threads use it
EEL 6897 19
Double-checked Locking Idiom
public class ResourceController {
private static Resource resource = null;
public static Resource getResource() {
if(resource == null) {
synchronized (ResourceController.class){
if(resource == null)
resource = new Resource();
}
}
return resource;
}
}
Why is this broken?
EEL 6897 20
The JMM Revisited I
• In the JMM, each thread is considered to has access
to its own working memory as well as the main
memory which is shared between all threads
• This working memory is used to hold copies of the
data which resides in the shared main memory
• It is an abstraction of data held in registers or data
held in local caches on a multi-processor system
EEL 6897 21
The JMM Revisited II
• It is a requirement that:
– inside a synchronized method or statement any read of a shared
variable must read the value from main memory
– before a synchronized method or statement finishes, any variables
written to during the method or statement must be written back to
main memory
• Data may be written to the main memory at other times as well,
however, the programmer just cannot tell when
• Code can be optimised and reorder as long as long as it
maintains “as-if-serial” semantics
• For sequential Java programs, the programmer will not be able
to detect these optimisations and reordering
• In concurrent systems, they will manifest themselves unless the
program is properly synchronized
EEL 6897 22
Double-checked Locking Idiom Revisited I
• Suppose that a compiler implements the resource =
new Resource() statement logically as follows
tmp = create memory for the Resource class
// tmp points to memory
Resource.construct(tmp)
// runs the constructor to initialize
resource = tmp // set up resource
EEL 6897 23
Double-checked Locking Idiom Revisited II
• Now as a result of optimizations or reordering,
suppose the statements are executed in the following
order
tmp = create memory for the Resource class
// tmp points to memory
resource = tmp
Resource.construct(tmp)
// run the constructor to initialize
There is a period of time when the resource reference
has a value but the Resource object has not been
initialized!
EEL 6897 24
Warning
• The double-checked locking algorithm illustrates that
the synchronized method (and statement) in Java
serves a dual purpose
• Not only do they enable mutual exclusive access to a
shared resource but they also ensure that data
written by one thread (the writer) becomes visible to
another thread (the reader)
• The visibility of data written by the writer is only
guaranteed when it releases a lock that is
subsequently acquired by the reader
EEL 6897 25
Asynchronous Thread Control
• Early versions of Java allowed one thread to
asynchronously effect another thread through
public class Thread extends Object
implements Runnable {
...
public final void suspend();
public final void resume();
public final void stop();
public final void stop(Throwable except)
throws SecurityException;
...
}
All of the above methods are now obsolete
and therefore should not be used
EEL 6897 26
Thread Interruption
public class Thread extends Object
implements Runnable {
...
public void interrupt();
// Send an interrupt to the associated thread
public boolean isInterrupted();
// Returns true if associated thread has been
// interrupted, the interrupt status is
// left unchanged
public static boolean interrupted();
// Returns true if the current thread has been
// interrupted and clears the interrupt status
...
}
EEL 6897 27
Thread Interruption
When a thread interrupts another thread:
• If the interrupted thread is blocked in wait,
sleep or join, it is made runnable and the
InterruptedException is thrown
• If the interrupted thread is executing, a flag is set
indicating that an interrupt is outstanding; there
is no immediate effect on the interrupted thread
• Instead, the called thread must periodically test
to see if it has been interrupted using the
isInterrupted or interrupted methods
– If the thread doesn’t test but attempts to blocks, it is made
runnable immediately and the InterruptedException is
thrown
EEL 6897 28
Summary
• True monitor condition variables are not directly
supported by the language and have to be programmed
explicitly - Java concurrency utilities
• Communication via unprotected data is inherently unsafe
• Asynchronous thread control allows thread to affect the
progress of another without the threads agreeing in
advance as to when that interaction will occur
• There are two aspects to this: suspend and resuming a
thread (or stopping it all together), and interrupting a
thread
• The former are now deemed to be unsafe due to their
potential to cause deadlock and race conditions
• The latter is not responsive enough for real-time systems
EEL 6897 29
Further Reading and Exercises
• Find out more about the Java 1.5 memory model (look
for JSR 133 on the web - this was the name of the
group that developed it.)
• Do the Readers Writers Exercise
• Do a paper exercise (I am not sure we have Java 1.5
with the concurrency utilities yet) for the Readers
Writers using ReentrantLocks
• Do Thread Interruption Exercise
• Do question 1 in the 2004 examination papers
EEL 6897 30
Download