Implementarea Concurent, ei în limbaje de
programare
JAVA 3
Traian Florin S, erbănut, ă
FMI @ UNIBUC
1
Synchronization using class Lock
Readers-Writers Model
Executor, ExecutorService, Executors
Sequential consistency
2
Synchronization using class Lock
Lock vs synchronized
synchronized (ob) { ... }
• Uses the intrinsic lock of the object
• Forces a structured approach to synchronization:
• intrinsic lock automatically released upon exiting the block
Objects of classes implementing the Lock interface
• Manage their own locks
• more flexible
• threads can access locks in any order;
• making sure the lock is released falls on the programmer
Lock l = ...
l.lock();
try {
...
} finally { l.unlock(); }
3
Avoiding interference using Lock
public class NonInterference {
static int c = 0;
public static void main(String[] args)
throws InterruptedException {
Lock cLock = new ReentrantLock();
Thread myThread = new Thread(() -> {
for (int x = 0; x < 5000; ++x)
{cLock.lock(); try {c++;} finally {cLock.unlock();}}
});
myThread.start();
for (int x = 0; x < 5000; ++x)
{cLock.lock(); try {c--;} finally {cLock.unlock();}}
myThread.join();
System.out.println("c = " + c);
}
}
4
interface Lock
void lock() Acquires lock associated to object; may block
void unlock() Releases lock associated to object; not blocking
void lockInterruptibly() Acquires associated lock - if the
thread is not interrupted; may block
boolean tryLock() Acquires the lock if available when invoked;
not blocking
boolean tryLock(long time, TimeUnit unit) Acquires the
lock if it becomes available within the given time
Condition newCondition() Creates a new Condition variable
corresponding to the current Lock instance.
5
interface Condition
• Implements methods similar to wait and notify for Locks
await(), await(long time, TimeUnit unit) Current
threads waits for a signal on this condition
signal(), signalAll() one/all thread(s) waiting for this
condition is/are signaled to wake.
• Similarly to Object, conditions are linked to a Lock object
• lock must be acquired before calling wait()
• Multiple conditions can coexist for the same lock
6
Better Producer-Consumer Solution using Lock and Condition
(I)
public class Cell<T> implements DropBox<T> {
private T cell = null;
private boolean open = true;
private Lock cellLock;
private Condition putCond;
private Condition getCond;
public Cell() {
cellLock = new ReentrantLock();
putCond = cellLock.newCondition();
getCond = cellLock.newCondition();
}
7
Better Producer-Consumer Solution using Lock and Condition
(put)
@Override public boolean put(T message)
throws InterruptedException {
cellLock.lock();
try {
while (open && cell != null) putCond.await();
if (!open) return false;
cell = message;
getCond.signal();
return true;
} finally {
cellLock.unlock();
}
}
8
Better Producer-Consumer Solution using Lock and Condition
(take)
@Override public Optional<T> take()
throws InterruptedException {
cellLock.lock();
try {
while (open && cell == null) getCond.await();
if (open) putCond.signal();
T message = cell;
cell = null;
return Optional.ofNullable(message);
} finally {
cellLock.unlock();
}
}
9
Better Producer-Consumer Solution using Lock and Condition
(close)
@Override public void close() {
cellLock.lock();
try {
open = false;
putCond.signalAll();
getCond.signalAll();
} finally {
cellLock.unlock();
}
}
}
Notes
• using different conditions for put and take
• Advantage: no need to wake all threads each time something
changes
10
Readers-Writers Model
Readers-Writers Interaction Model
• Multiple threads need concurrent access to the same resource
• Some threads write (writers), others only read (readers).
• The resource can be read simultaneously by multiple readers
• The resource cannot be written simultaneously
• The resource cannot be simultaneously read and written
Expressed in Java through the ReadWriteLock interface
readLock() yields the lock for readers
writeLock() yields the lock for writers
11
Addressing non-interference using ReadWriteLock (reader
thread)
public class NonInterferenceRW {
static int c;
public static void main(String[] args)
throws InterruptedException {
final ReadWriteLock lock = new ReentrantReadWriteLock();
Runnable reader = () -> {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()
+ " counter: " + c);
} finally { lock.readLock().unlock(); }
};
12
Addressing non-interference using ReadWriteLock (writer
threads)
Thread incThread = new Thread(() -> {
for (int x = 0; x < 5000; ++x) {
lock.writeLock().lock(); try {
for (int i = 0; i < 5; i++)
{ c++; Thread.yield(); }
} finally { lock.writeLock().unlock(); }
}});
Thread decThread = new Thread(() -> {
for (int x = 0; x < 5000; ++x) {
lock.writeLock().lock(); try {
for (int i = 0; i < 5; i++)
{ c--; Thread.yield(); }
} finally { lock.writeLock().unlock(); }
}});
13
Addressing non-interference using ReadWriteLock (main setup)
incThread.start();
decThread.start();
ExecutorService executorService =
Executors.newFixedThreadPool(2);
for (int i = 0; i < 100; i++)
executorService.execute(reader);
executorService.shutdown();
while (!executorService.isTerminated()) {
executorService.awaitTermination(1, TimeUnit.SECONDS);
}
incThread.join();
decThread.join();
System.out.println("c = " + c);
}
}
14
Executor, ExecutorService,
Executors
Executor and ExecutorService interfaces
Executor is an abstraction over Thread
void execute(Runnable task) Executes a task; whether
blocking or non-blocking, implementation decides
ExecutorService enhances the Executor functionalities
• . . . with ways to control execution
for (int i = 0; i < 100; i++) executorService.execute(reader);
executorService.shutdown();
while (!executorService.isTerminated()) {
executorService.awaitTermination(1, TimeUnit.SECONDS);
}
• . . . with ways to wait for results (next section)
15
class Executors
Provides static methods for creating ExecutorServices
static ExecutorService newCachedThreadPool() creates a
pool of threads to execute tasks. Reuses ended
threads. Uses a queue for tasks.
static ExecutorService newFixedThreadPool(int nThreads)
creates pool of nThreads threads to execute tasks.
Uses a (blocking) queue for scheduling tasks.
static ExecutorService newSingleThreadExecutor()
creates a single-threaded pool for executing tasks.
Uses a queue for tasks.
16
Asynchronous functions in Java: Callable, Future
Callable<T>
• Abstraction for actions (concurrent or not) returning a result
• think of it as a Runnable returning a value.
Callable<Integer> giveMeFive = () -> 5;
Future<T>
The promise of an Executor to deliver the result of a Callable
T get() Obtains the result promised by the Future. May block
waiting for the action to conclude
The submit method from ExecutorService
Future<T> submit(Callable<T> task) Schedules a task for
execution, returning a promise to deliver its result
17
Future
• ExecutorService.submit(task) returns a Future
• does not block; calling thread continues executing tasks
• even for a single-threaded Executor
• Result promised Future is obtained using get()
• get() blocks calling thread until result becomes available.
• if the thread executing the Callable is interrupted, throws
• isDone() called for a Future tells whether result is available.
18
Future example using isDone
public class FutureExample {
public static void main(String[] args) throws Exception {
ExecutorService executor =
Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(() -> {
int time = ThreadLocalRandom.current().nextInt(1000, 5000);
Thread.sleep(time); return time;
});
while (!future.isDone())
{ System.out.println("Task not done ...");
Thread.sleep(500); }
System.out.println("Task duration: " + future.get());
executor.shutdown();
}
}
19
ExecutorService: invokeAll and invokeAny
List<Future<T>>
invokeAll(Collection<? extends Callable<T>> tasks)
Schedules all tasks for execution
• returns a Future for each task
• Blocks untill all tasks are done.
T invokeAny(Collection<? extends Callable<T>> tasks)
Schedules all taks for execution
• returns one of the results
• the other tasks are canceled
20
invokeAll example
Optional<To> mapReduce(Collection<From> c,
Function<From, To> map,
BinaryOperator<To> reduce)
throws Exception {
return
executor.invokeAll(
c.stream()
.map( e -> ((Callable<To>) () -> map.apply(e)) )
.collect(Collectors.toList())
).stream()
.map(InvokeAllExample::get)
.reduce(reduce);
}
21
invokeAny example
• Allows concurrent execution of multiple tasks
• Choses the task finishing first
• Useful when having more methods for solving the same problem
int search(Integer key)
throws ExecutionException, InterruptedException {
return executor.invokeAny(Arrays.asList(
() -> search1(key),
() -> search2(key),
() -> search3(key)));
}
22
Sequential consistency
Concurrent objects and serial specification
Concurrent objects
Shared entities upon which a thread can perform certain operations
Serial specification
The valid behavior of a concurrent object (in isolation)
• we only observe the operations performed on that object
23
Concurrent object example: shared memory location
Operations
read Reading the value from the memory location
write Writing a value into the memory location
Serial specification
Every read operation yields the value written by the latest write.
24
Concurent object example: mutex (lock)
Operations
lock acquires the lock
unlock releases the lock
Serial specification
• #lock - #unlock is either 0 or 1 for all prefixes
• sequence of lock unlock pairs
• last unlock can miss
• Consecutive lock - unlock operations belong to same thread
25
Formalizing sequential consistency [Attiya&Welch, 1994]
Legal execution
• Any concurrent object satisfies its serial specification
• When restricting the execution to that object alone.
Execution reordering
A permutation of the events in the original execution such that its
restriction to each thread is the same as in the original execution
Sequential consistent execution
An execution admitting a legal reordering
Guarantees offered by sequential consistency
• Execution happens as if it were legal
• Programmer can assume each concurrent object behaves
according to its serial specification
26
The Java memory model
• Sequential consistency <= program is correctly synchronized
Correctly synchronized no data-races
Data-race conflictual access not ordered by happens-before
concurrent access to same location, one is write
The Happens-before relation hb(x,y)
Thread order x before y within the same thread => hb(x,y)
Synchronization x synchronizes with subsequent action y =>
hb(x,y)
Transitivity hb(x,y) and hb(y,z) => hb(x,z)
Constructor-Finalizer If x is the end of a constructor and y is the
call to finalize for that object => hb(x,y)
27
Synchronization order
• unlock synchronizes with subsequent locks on same monitor
• write of volatile v synchronizes with subsequent reads of v
• Thread start synchronizes with first operation in that thread
• Final operation in thread synchronizes with any operation
detecting that the thread has ended
28