UnboundedQueue
Enqueue public void enq (T value) {
enqLock.lock();
try {
Node nd = new Node(value);
tail.next = nd;
tail = nd;
} finally {
enqLock.unlock();
}
}
LockFreeQueue
Enqueue public void enq(T item) {
if (item == null) throw new NullPointerException();
Node node = new Node(item);
while (true) {
Node last = tail.get();
Node next = last.next.get();
if (last == tail.get()) {
if (next == null) {
if (last.next.compareAndSet(next, node))
tail.compareAndSet(last, node); return;
} else {
tail.compareAndSet(last, next);}}}}
UnboundedQueue
ProgressGuarantee: Starvation Freedom (assuming lock is starvation-free)
if all pending method calls continue to take steps, then every pending method call completes in a finite number of steps
this is blocking progress: if even one thread stops taking steps, then all other threads can be impeded
Question. When is this “good?”
LockFreeQueue
ProgressGuarantee: Lock Freedom
if some pending method call makes progress, then some pending method call completes in a finite number of steps
this is nonblocking progress: if some threads stall, others are still guaranteed to make progress
Blocking Progress:
Nonblocking Progress:
Demo: concurrent-queues.zip
class Peterson implements Lock {
private boolean[] flag = new boolean[2];
private int victim;
public void lock () {
int i = ThreadID.get(); int j = 1 - i;
flag[i] = true; // set my flag
victim = i; // set myself to be victim
while (flag[j] && victim == i) {}; }
public void unlock () { int i = ThreadID.get();
flag[i] = false; }
}
Download: peterson-lock.zip
Peterson lock assumes 2 threads, with IDs 0
and 1
We’ll use this thread
to increment a counter
public class PetersonThread extends Thread {
private int id;
private LockedCounter ctr;
private int numIncrements;
public PetersonThread (id, ctr, numIncrements) {
super(); this.id = id; this.ctr = ctr;
this.numIncrements = numIncrements; }
public int getPetersonId() { return id; }
@Override
public void run () {
for (int i=0; i<numIncrements; ++i) { ctr.increment(); }}
}
Next week: A better way
PetersonLock
class PetersonLock {
private boolean[] flag = new boolean[2];
private int victim;
public void lock () {
int i = ((PetersonThread)Thread.currentThread())
.getPetersonId();
int j = 1 - i; flag[i] = true; victim = i;
while (flag[j] && victim == i) {};
}
public void unlock () {...}
}
public class LockedCounter {
private int count = 0;
PetersonLock lock = new PetersonLock();
public void increment () {
lock.lock();
try { ++count; }
finally { lock.unlock(); }
}
public int read () {
return count;
}
}
What happened?
volatile
VariablesJava can make variables visible between threads:
volatile
keywordvolatile
are atomicDrawbacks:
volatile
variables are less efficientcount++
not atomicvolatile SomeClass...
, only the reference is treated as volatilevolatile
?PetersonLock
?
flag
?victim
?LockedCounter
?
count
?Only primitive datatypes can be volatile
volatile boolean[] flag
makes the reference volatile
, not the data itselfHow to fix this?
Just make 2 boolean
variables, flag0
and flag1
peteson-lock.zip
What have we done?
Theory and practice converge!
The Good:
The Bad:
PetersonThread
to assign IDsQuestion. How could we lock more simply?
Use more advanced Atomic
Objects!
Introducing the AtomicBoolean
class:
var ab = new AtomicBoolean(boolean value)
make an AtomicBoolean
with initial value value
ab.get()
return the current valueab.getAndSet(boolean newValue)
atomically set the value to newValue
and return the old valueab.compareAndSet(boolean expected, boolean new)
atomically update to new if previous value was expected
and return whether or not the value was updatedQuestion. How could we use AtomicBoolean
s to design a simpler lock?