The Test-and-set (TAS) Lock:
public class TASLock {
AtomicBoolean isLocked = new AtomicBoolean(false);
public void lock () {
while (isLocked.getAndSet(true)) {}
}
public void unlock () {
isLocked.set(false);
}
}
Decreases with number of processors
n elapsed time (ms)
1 109
2 200
3 492
4 494
5 576
6 804
7 796
8 1001
9 1130
10 1128
11 1123
12 1260
13 1320
14 1476
15 1610
16 1646
17 1594
18 1198
19 1755
20 1139
21 1940
22 2033
23 2133
24 2258
25 2125
26 2578
27 2211
28 2432
29 2188
30 2663
31 2579
Doing same number of ops takes 25 times as long on 31 threads as on 1 thread!
public class TASLock {
AtomicBoolean isLocked = new AtomicBoolean(false);
public void lock () {
while (isLocked.getAndSet(true)) {}
}
public void unlock () {
isLocked.set(false);
}
}
Each thread calls getAndSet
constantly!
Only try to getAndSet
if previously saw lock was unlocked
public void lock () {
while (true) {
while (isLocked.get()) {};
if (!isLocked.getAndSet(true)) {
return;
}
}
}
getAndSet
$\implies$ better performance?Same test as before on Remus and Romulus (1M accesses):
n elapsed time (ms)
1 108
2 202
3 206
4 212
5 253
6 237
7 218
8 242
9 275
10 250
11 214
12 239
13 216
14 265
15 219
16 283
17 266
18 287
19 279
20 286
21 231
22 294
23 300
24 243
25 293
26 303
27 300
28 294
29 302
30 303
31 305
32 309
Now: performance with 32 threads is less than 3 times slower than without contention!
Backing off under contention
public void lock () {
while (true) {
while (isLocked.get()) {};
if (!isLocked.getAndSet(true)) {
return;
}
}
}
if (...)
but fail to acquire lock, there are other threads attempting to acquire lock
Store:
MIN_DELAY
, MAX_DELAY
(constants)limit
initialized to MIN_DELAY
When contention detected:
delay
between 0
and limit
limit = Math.min(2 * limit, MAX_DELAY)
delay
time before attempting to acquire lock againdelay
Is it practical?
n elapsed time (ms)
1 148
2 234
3 250
4 226
5 216
6 230
7 255
8 267
9 262
10 269
11 271
12 276
13 265
14 298
15 289
16 267
17 296
18 292
19 308
20 280
21 290
22 302
23 261
24 304
25 313
26 317
27 317
28 331
29 322
30 313
31 315
32 314
Not better than TTAS for tested parameters.
Textbook Backoff
uses Thread.sleep(...)
sleep
tells OS scheduler not to schedule thread for a while
while (true) {};
sleep
delay is 1 ms private void spin (long delay) {
long start = System.nanoTime();
long cur = System.nanoTime();
while (cur - start < delay) {
cur = System.nanoTime();
};
}
Exponential backoff is:
So far:
TASLock
TTASLock
BackoffLock
are deadlock-free, but not starvation free!
Question. How to achieve starvation freedom?
Represent threads waiting for lock as a queue
true
I want/have lockfalse
I don’t want/have locktail
node storing false
A
Acquires LockB
Calls lock()
A
Releases LockB
Acquires LockWe need a Node
for each thread!