Download Atomic Variable and Non Blocking Synchronization - Programming Language | CMSC 433 and more Exams Programming Languages in PDF only on Docsity!
Atomic Variables
Nonblocking Synchronization
A Locking Counter
public final class Counter {
private long value = 0;
public synchronized long getValue() {
return value;
public synchronized long increment() {
return ++value;
Java.util.concurrent Performance
• Many java.util.concurrent classes perform better
then synchronized alternatives. Why?
– Atomic variables & nonblocking synchronization
• We’ve already talked about atomic variables.
• Nonblocking algorithms are concurrent algorithms
that derive their thread safety from low-level
atomic hardware primitives (not locks).
Disadvantages of Locking
• When a thread fails to acquire lock it can be
suspended
- Context switching & resumption can be expensive
• When waiting for a lock thread can’t do anything
• If thread holding lock is delayed, no thread that needs
that lock can progress
- Priority inversion: low priority thread has lock needed by a high priority thread
• Caveat: contention, rather than locking, is the real
issue. YMMV
Simulated (CAS)
public class SimulatedCAS { // not implemented this way! private int currValue; public synchronized int get() {return currValue;} public synchronized int compareAndSwap ( int expectedValue, int newValue) { if (currValue == expectedValue) currValue = newValue; return currValue ; } public synchronized boolean compareAndSet(int expectedValue, int newValue) { return (expectedValue == compareAndSwap(expectedValue, newValue)); } }
A Nonblocking Counter
public class NonblockingCounter { private AtomicInteger value; public int getValue() { return value.get(); } public int increment() { int v; do { v = value.get(); }while (!value.compareAndSet(v, v + 1)); return v + 1; } }
Atomic Variables
• Generalization of volatile variables
• Allows atomic read-modify-write operations
without intrinsic locking
• Scope of contention limited to a single variable
• Faster than locking -- no scheduling impact
• Like volatiles, can’t synchronize two atomic vars
• Doesn’t support atomic check-then-act sequences
Updating Complex Objects
• Example: Want to manage two related variables
• Can’t do that with volatiles
• Idiom: turn compound update into single update
Performance Comparison
• Will show two implementations of a psuedo-
random number generator (PRNG)
– One uses locks: ReentrantLockPseudoRandom.java
– One is nonblocking: AtomicPseudoRandom.java
• PRNG issues
– Next value based on last value, so you need to
remember last value
• How do lock-based and non-lock-based
implementations compare?
ReentrantLockPseudoRandom
public class ReentrantLockPseudoRandom extends PseudoRandom { private final Lock lock = new ReentrantLock(false); private int seed; ReentrantLockPseudoRandom(int seed) {this.seed = seed;} public int nextInt(int n) { lock.lock(); try { int s = seed; seed = calculateNext(s); int remainder = s % n; return remainder > 0? remainder : remainder + n; } finally { lock.unlock();} } }
AtomicPseudoRandom
public class AtomicPseudoRandom extends PseudoRandom { private AtomicInteger seed; AtomicPseudoRandom(int seed) {this.seed = new AtomicInteger(seed);} public int nextInt(int n) { while (true) { int s = seed.get(); int nextSeed = calculateNext(s); if (seed.compareAndSet(s, nextSeed)) { int remainder = s % n; return remainder > 0? remainder : remainder + n; } } } }
Atomic Updates / Lock Updates
0 1 2 3 4 5 6 7 100 50 25 10 5 #Threads
Nonblocking Stack
• See: ConcurrentStack.java & SynchStack.java
Nonblocking Stack
public class ConcurrentStack { private static class Node { public final E item; public Node next; public Node(E item) { this.item = item; } } AtomicReference<Node> top = new AtomicReference<Node>(); public void push(E item) { Node newHead = new Node(item); Node oldHead; do { oldHead = top.get(); newHead.next = oldHead; } while (!top.compareAndSet(oldHead, newHead)); }
Nonblocking Stack
public E pop() { Node oldHead; Node newHead; do { oldHead = top.get(); if (oldHead == null) return null; newHead = oldHead.next; } while (!top.compareAndSet(oldHead, newHead)); return oldHead.item; } }
A Nonblocking Queue
• Rule of thumb– limit change to one variable
• Harder for a Queue because we need to update
head and tail
• See:
Michael & Scott Nonblocking Queue
• Queue with two elements in quiescent state
Michael & Scott Nonblocking Queue
• Queue in intermediate state during insertion
– After the new element is added but before the tail
pointer is updated
Michael & Scott Nonblocking Queue
• Queue in quiescent state again after the tail pointer
is updated
Michael & Scott Nonblocking Queue
• Observation: if tail.next is non-null, then a
operation is in progress
• If a thread finds an operation in progress, it will
try to advance tail to return queue to stable state
– Then it will reload tail and repeat process
ConcurrentQueue
public E take() { for (;;) { // # Keep trying until take is done Node oldHead = head.get(); // get current head Node oldTail = tail.get(); // get current tail Node oldHeadNext = oldHead.next.get(); // get current head.next if (oldHead == head.get()) { // Are head, tail, and next consistent? if (oldHead == oldTail) { // Queue empty or tail being updated? if (oldHeadNext == null) // Is queue empty? return null; // Queue is empty, can't take tail.compareAndSet(oldTail, oldHeadNext); // tail updated. try to advance it } else { // No need to deal with tail if (head.compareAndSet(oldHead, oldHeadNext)) return oldHeadNext.item; } } }