CM0247 Application Building with Java

lightnewsΛογισμικό & κατασκευή λογ/κού

18 Νοε 2013 (πριν από 3 χρόνια και 10 μήνες)

97 εμφανίσεις

School of Computer Science
and Informatics
Cardiff University
CM0247 Application Building with Java
II Concurrency
II.3 Thread Safety
F.C.Langbein
Version 1.1
Object States
Thread-safety is all about states
Object’s data,stored in state variables,e.g.instance and static fields
Particularly,shared mutable state
State variables accessed by multiple threads
Whose value can change during their lifetime
Thread safety:protect shared mutable state
Program is broken if multiple threads access same mutable state
variable without suitable synchronisation
To fix it
– Don’t share sate variable across threads
– Make state variable immutable
– Use synchronisation for state variable access
Thread-safety part of class design,supported by object orientation
Encapsulation,immutability,clear spec.of invariants,etc.help
F.C.Langbein,CM0247 Application Building with Java – II Concurrency;II.3 Thread Safety 1
Thread Safety
Class is thread safe:behaves correctly when accessed from multiple
threads
Independent of scheduling or execution order by runtime environment
Without additional synchronisation,etc.from calling code
Note:thread-safe classes 6= thread-safe program
Naturally thread safe:download images in parallel
Undesirable effects:speech synthesiser accessed in parallel
Errors:banking system controlling cash withdrawals
1 cash = b;//Get current balance
2 cash = cash - a;//Withdraw a
3 b = cash;//Store new balance
Concurrent execution causes problems
– E.g.withdraw 25 from100 twice
in two threads concurrently
Variable b is shared,others are local
F.C.Langbein,CM0247 Application Building with Java – II Concurrency;II.3 Thread Safety 2
Stateless Methods
Stateless methods use no fields or reference fields from other classes
Transient state of computation exists only in local variables
State exists only on stack
Sate is only accessible from executing thread
Stateless objects are always thread-safe
Factoriser service
1 public class StatelessFactoriser {
2 public void service (Request req,Response resp) {
3 BigInteger i = extractFromRequest (req);
4 BitInteger[] factors = factor (i);
5 encodeIntoResponse (resp,factors);
6 }
7 }
F.C.Langbein,CM0247 Application Building with Java – II Concurrency;II.3 Thread Safety 3
Atomicity
Naive way of adding “hit counter” to factoriser
1 public class UnsafeCountingFactoriser {
2 private long count = 0;
3 public long getCount () { return count;}
4 public void service (Request req,Response resp) {
5 BigInteger i = extractFromRequest (req);
6 BitInteger[] factors = factor (i);
7 ++count;
8 encodeIntoResponse (resp,factors);
9 }
10 }
++count is not atomic
Three operations:fetch value,add,write value
UnsafeCountingFactoriser is not thread-safe
Read-modify-write operation:resulting state comes from previous state
Leads to race conditions
F.C.Langbein,CM0247 Application Building with Java – II Concurrency;II.3 Thread Safety 4
Race Condition
Race condition:correctness of results depends on relative timing or
interleaving of multiple threads during runtime
Check-then-act:potentially stale test used to decide what to do next
Lazy initialisation
1 public class LazyInitRace {
2 private ExpensiveObject instance = null;
3 public ExpensiveObject getInstance () {
4 if (instance == null)
5 instance = new ExpensiveObject ();
6 return instance;
7 }
In multi-threaded environment multiple instances may be created and
returned,even if not intended (may e.g.break a registration service)
Race conditions do no always result in failure
Depends on timing
But they can cause serious errors
F.C.Langbein,CM0247 Application Building with Java – II Concurrency;II.3 Thread Safety 5
Compound Actions
Certain operation sequences must be atomic to avoid race conditions
Indivisible,relative to other operations on same sate
Operations A and B are atomic with respect to each other
If,from perspective of thread executing A,when another thread
executes B,either all or none of B has been executed
An atomic operation is one that is atomic with respect to all operations,
including itself,that operate on the same state
java.concurrent.atomic package has atomic variable classes
1 public class CountingFactoriser {
2 private final AtomicLong count = new AtomicLong (0);
3 public long getCount () { return count.get ();}
4 public void service (Request req,Response resp) {
5 BigInteger i = extractFromRequest (req);
6 BitInteger[] factors = factor (i);
7 count.incrementAndGet ();
8 encodeIntoResponse (resp,factors);
9 } }
F.C.Langbein,CM0247 Application Building with Java – II Concurrency;II.3 Thread Safety 6
Locking
Multiple variables may not be independent
Must be updated in same atomic operation to preserve invariants
E.g.a caching factoriser remembering the last number and factors
– Last number and factors fields are not independent
– Cannot use individual atomic variable classes
– Must ensure operations on both variables are atomic
– Invariant:product of cached factors must be last number
Requires explicit mechanism to lock state of object
While state is locked by one thread,no other thread can access it
Access to state (or resource) is guarded by a monitor
At most one thread is allowed access to state
Thread wishing to access state (i) acquires lock,(ii) executes
operations and (iii) releases lock
If state is locked,it waits until lock is released by other thread
F.C.Langbein,CM0247 Application Building with Java – II Concurrency;II.3 Thread Safety 7
Intrinsic Locks
Java’s built-in locking mechanism:synchronized block
Requires reference to object serving as lock
1 synchronized (lock) {
2...//Access or modify shared state guarded by lock
3 }
Synchronized method:shorthand for synchronized block spanning
whole method guarded by its object
Static synchronized methods are guarded by class object of lock
Intrinsic or monitor locks
Each Java object can implicitly act as lock
Acquired and released automatically at beginning/end of
synchronized block
Intrinsic blocks are mutexes (mutual exclusion locks)
At most one thread may own the lock (for atomicity)
F.C.Langbein,CM0247 Application Building with Java – II Concurrency;II.3 Thread Safety 8
Naive Thread-Safe Caching Factoriser
1 public class SynchronisedCachingFactoriser {
2 private BigInteger lastNumber;
3 private BigInteger[] lastFactors;
4 public synchronized void service(Request req,Response resp) {
5 BigInteger i = extractFromRequest (req);
6 if (i.equals (lastNumber)) {
7 encodeIntoResponse (resp,lastFactors);
8 } else {
9 BigInteger[] factors = factor (i);
10 lastNumber = i;
11 lastFactors = factors;
12 encodeIntoResponse (resp,lastFactors);
13 }
14 }
15 }
Awful performance
Multiple factorisation requests are executed sequentially
F.C.Langbein,CM0247 Application Building with Java – II Concurrency;II.3 Thread Safety 9
Reentrancy
Intrinsic locks are reentrant
If a thread tries to acquire lock it already holds,request succeeds
Java keeps acquisition count and owning thread
Acquisition count 0 means lock is available
Thread acquiring previously unheld lock sets count to 1
If same thread acquires lock again,count is increased
If lock is released,count is decremented
When count is 0,lock is released
Supports encapsulation of locking behaviour
1 public class Widget {
2 public synchronized void doSomething () {...} }
3 public class LogWidget extends Widget {
4 public synchronized void doSomething () {
5 System.out.println ("Widget.doSomething () called");
6 super.doSomething ();//Deadlocks without reentrant locks
7 } }
F.C.Langbein,CM0247 Application Building with Java – II Concurrency;II.3 Thread Safety 10
Guarding States
Synchronized blocks make compound actions atomic
Hold lock for entire duration of compound action
Needed for read-modify-write,check-then-act,etc.
Note,all accesses to mutable variables need to be synchronized
Must use the same lock object
Not just for write operations!
A lock guards the variable
May use object of variable to provide the lock object
But other lock objects may be created
A mutable variable should by guarded by exactly one lock
Clearly document which lock that is
Multiple variables with invariants (a state) should also be guarded by
one lock
F.C.Langbein,CM0247 Application Building with Java – II Concurrency;II.3 Thread Safety 11
Guarding States
Common pattern:put all mutable variables in an object and use the
object’s intrinsic lock for access
But other patterns may be used
For every invariant that involves more than one variable
All variables involved in invariant must be guarded by the same lock
object
Making everything synchronised is not the solution to prevent race
conditions
Sometimes this is too much synchronisation
– See synchronised cached factoriser
Liveness or performance issues
Sometimes this is still not enough synchronisation
– E.g.put-if-absent pattern
1 if (!vector.contains (element))
2 vector.add (element);
F.C.Langbein,CM0247 Application Building with Java – II Concurrency;II.3 Thread Safety 12
Guarding Arrays
Special case using synchronized block:arrays
Methods only work on objects,not arrays
Synchronised access to whole array of objects requires explicit
synchronized block,not a synchronized method
E.g.sorting an array
1 public static void sortIntArray (int[] a) {
2 synchronized (a) {
3...//sort array a
4 }
5 }
F.C.Langbein,CM0247 Application Building with Java – II Concurrency;II.3 Thread Safety 13
Performance
Synchronised caching factoriser has poor performance
Whole method is synchronised which effectively requires sequential
execution
Instead use synchronized blocks inside methods to reduce locking
But scope of block should not be limited too much
– Keep operations requiring synchronisation in one block
– Exclude long running operations
Tension between simplicity and performance
Deciding on size of synchronised block often requires tradeoff
between competing design forces:
safety,simplicity,performance,etc.
Avoid premature sacrifice of simplicity (and with it safety) for
performance
Avoid holding locks for lengthy computations or operations that have risk
of not completing quickly,e.g.network or user I/O
F.C.Langbein,CM0247 Application Building with Java – II Concurrency;II.3 Thread Safety 14
Caching Factoriser
1 public class CachingFactoriser {
2
3 private BigInteger lastNumber;
4 private BigInteger[] kastFactors;
5 private long hits;
6 private long cacheHits;
7
8 public synchronized long getHits () {
9 return hits;
10 }
11
12 public synchronized double getCacheHitRatio () {
13 return (double) cacheHits/(double) hits;
14 }
15
16
17
18
19
20
F.C.Langbein,CM0247 Application Building with Java – II Concurrency;II.3 Thread Safety 15
Caching Factoriser
21 public void service (Request req,Response resp) {
22 BigInteger i = extractFromRequest (req);
23 ButInteger[] factors = null;
24 synchronized (this) {
25 ++hits;
26 if (i.equals (lastNumber)) {
27 ++cacheHits;
28 factors = lastFactors.clone ();
29 }
30 }
31 if (factors == null) {
32 factors = factor (i);
33 synchronised (this) {
34 lastNumber = i;
35 lastFactors = factors.clone ();
36 }
37 }
38 encodeIntoResponse (resp,lastFactors);
39 }
40 }
F.C.Langbein,CM0247 Application Building with Java – II Concurrency;II.3 Thread Safety 16