Progress Conditions:
Wait-freedom: pending method invocation always completes in a finite number of steps
Lock-freedom: among all pending method calls, some method completes in a finite number of steps
Observed: wait-free $\implies$ lock-free
public class TwoCounter {
int[] counts = new int[2];
public void increment (int amt) {
int i = ThreadID.get(); // thread IDs are 0 and 1
int count = counts[i];
counts[i] = count + amt;
}
public int read () {
int count = counts[0];
count = count + counts[1];
return count;
}
}
AtomicInteger
class:
boolean compareAndSet(int expectedValue, int newValue); // ATOMIC
Specification for AtomicInt ai
:
ai
’s value is expectedValue
, then
ai
’s value is newValue
true
ai
’s value is not expectedValue
, then
ai
’s value is unchangedfalse
public class SillyCounter {
AtomicInteger ai = new AtomicInteger(0);
public void increment (int amt) {
int val = ai.get();
while (!ai.compareAndSet(val, val + amt)) {
val = ai.get();
}
}
public int read () {
return ai.get();
}
}
SillyCounter
Work?public class SillyCounter {
AtomicInteger ai = new AtomicInteger(0);
public void increment (int amt) {
int val = ai.get();
while (!ai.compareAndSet(val, val + amt)) {
val = ai.get();
}
}
public int read () {
return ai.get();
}
}
SillyCounter
Lock-free? public void increment (int amt) {
int val = ai.get();
while (!ai.compareAndSet(val, val + amt)) {
val = ai.get();
}
}
SillyCounter
Wait-free? public void increment (int amt) {
int val = ai.get();
while (!ai.compareAndSet(val, val + amt)) {
val = ai.get();
}
}
Nonblocking progress
Blocking progress
public class SillyCounter {
private AtomicInteger ai = new AtomicInteger(0);
public void increment (int amt) {
int val = ai.get();
while (!ai.compareAndSet(val, val + amt)) {
val = ai.get();
}
}
}
Or
public class LockedCounter {
private Lock lock = new StarvationFreeLock();
int count = 0;
public void increment (int amt) {
lock.lock()
try {
count += amt;
} finally {
lock.unlock();
}
}
}
public class SillyCounter {
private AtomicInteger ai = new AtomicInteger(0);
public void increment (int amt) {
int val = ai.get();
while (!ai.compareAndSet(val, val + amt)) {
val = ai.get();
}
}
}
What happens to thread 1 if scheduler stops scheduling steps of thread 2?
public class LockedCounter {
private Lock lock = new StarvationFreeLock();
int count = 0;
public void increment (int amt) {
lock.lock()
try {
count += amt;
} finally {
lock.unlock();
}
}
}
What happens to thread 1 if scheduler stops scheduling steps of thread 2?
public class SillyCounter {
private AtomicInteger ai = new AtomicInteger(0);
public void increment (int amt) {
int val = ai.get();
while (!ai.compareAndSet(val, val + amt)) {
val = ai.get();
}
}
}
Is thread 1 guaranteed to make progress under fair scheduler?
public class LockedCounter {
private Lock lock = new StarvationFreeLock();
int count = 0;
public void increment (int amt) {
lock.lock()
try {
count += amt;
} finally {
lock.unlock();
}
}
}
Is thread 1 guaranteed to make progress under fair scheduler?
Nonblocking is not strictly superior to blocking!
Different implementations have different trade-offs
Counter
object, now with a lockRecall the Peterson lock:
class Peterson implements Lock {
private boolean[] flag = new boolean[2];
private int victim;
public void lock () {
int i = ThreadID.get(); // get my ID, 0 or 1
int j = 1 - i; // other thread's ID
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;
}
}
Peterson lock assumes 2 threads, with IDs 0
and 1
Manually set an ID for threads
public class PetersonThread extends Thread {
private int id;
private LockedCounter ctr;
private int numIncrements;
public PetersonThread (int id, LockedCounter ctr, int 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();
}
}
}
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 () {
int i = ((PetersonThread)Thread.currentThread()).getPetersonId();
flag[i] = false;
}
}
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
?PetersonLock
?LockedCounter
?PetersonLock
Againclass 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 () {
int i = ((PetersonThread)Thread.currentThread()).getPetersonId();
flag[i] = false;
}
}
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
LockedCounter
Againpublic 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 have we done?
Theory and practice converge!
PetersonLock
volatile
variables
n
registers (variables) for lock with n
threadsBetter locks through atomics:
AtomicBoolean
supports atomic operations in addition toab.getAndSet(boolean newValue)
ab
’s value to newValue
and returns previous valueHow could you use a single AtomicBoolean
public class TASLock {
AtomicBoolean isLocked = new AtomicBoolean(false);
public void lock () {
while (isLocked.getAndSet(true)) {}
}
public void unlock () {
isLocked.set(false);
}
}
public class TASLock {
AtomicBoolean isLocked = new AtomicBoolean(false);
public void lock () {
while (isLocked.getAndSet(true)) {}
}
public void unlock () {
isLocked.set(false);
}
}