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();
}
}
}
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 () {
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 SomeClass...
, only the reference is treated as volatilevolatile
?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 operationsab.getAndSet(boolean newValue)
ab
’s value to newValue
and returns previous valueHow could you use a single AtomicBoolean
to implement a lock?
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);
}
}
TASLock
Perform?TTASLock
In TASLock
, we do a lot of getAndSet()
operations under contention
public void lock () {
while (isLocked.getAndSet(true)) {}
}
Since getAndSet
is expensive, we could try:
getAndSet
if we’ve seen isLocked.get() == false
public void lock () {
while (true) {
while (isLocked.get()) {};
if (!isLocked.getAndSet(true)) {
return;
}
}
}
How does this affect performance?