/ Java  

Java Multithreading 18: Unfair lock

The previous two blogs analyzed the fair lock acquisition and release mechanism, this blog we will start to analyze the process of unfair lock acquisition and release.

Acquire unfair lock (based on JDK 11.0.5)

The process of acquiring locks is the same for unfair locks and fair locks. The difference between them is different mechanisms for trying to obtain locks. To put it simply, fair lock uses a fair strategy (ordering and waiting according to the waiting queue) every time it tries to acquire a lock. Unfair lock ignore the waiting queue, try to acquire the lock directly. If the lock is idle, you can acquire the status, then acquire the lock.

Next, let’s look at process to obtain the unfair lock.

1. lock()
lock() is implemented in the NonfairSync class of ReentrantLock

1
2
3
4
5
6
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}

lock() will first determine whether the lock is idle by compareAndSet(0, 1). If it is, the current thread directly acquires the lock. Otherwise, it calls acquire(1) to acquire the lock.

  1. compareAndSetState() is a CAS function. Its role is to compare and set the current lock state. If the lock state value is 0, set the lock state value to 1.
  2. The role of setExclusiveOwnerThread(Thread.currentThread()) is to set current thread as the holder of lock.

Comparison of lock() between fair lock and unfair lock

  • Fair lock - the lock() function of fair lock directly calls acquire(1).
  • Unfair locks - Unfair locks first determine whether the current lock status is idle, and if so, they do not queue and acquire the lock directly.

2. acquire()
acquire() is implemented in AQS

1
2
3
4
5
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
  • Current thread first tries to acquire the lock through tryAcquire(). If the acquisition is successful, return directly. If the attempt fails, enter the waiting queue and sort in sequence, and then acquire the lock.
  • If the current thread attempt fails, the current thread will be added to the end of the CLH queue (non-blocking FIFO queue) through addWaiter(Node.EXCLUSIVE).
  • Then, call acquireQueued() to acquire the lock. In acquireQueued(), the current thread will wait for all threads in the CLH queue to execute and release the lock before it can acquire the lock and return. If the current thread was interrupted during the sleep waiting process, call selfInterrupt() to generate an interrupt by itself.

Comparison of acquire() between fair lock and unfair lock
Fair locks and unfair locks differ only in the implementation of the tryAcquire() function. That is, they have different mechanisms for trying to acquire locks.

The tryAcquire() of the unfair lock is implemented in the NonfairSync class of ReentrantLock

1
2
3
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}

nonfairTryAcquire() is implemented in the Sync class of ReentrantLock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
final boolean nonfairTryAcquire(int acquires) {
// get current thread
final Thread current = Thread.currentThread();

// get lock state
int c = getState();

// c == 0 means lock is not owned by any thread!
if (c == 0) {
// If the lock is not owned by any thread , set the state of lock through the CAS function.
// At the same time, set current thread as the lock holder.
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// If the holder of lock is already current thread
// The status of the lock will be updated.
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

The role of tryAcquire() is to try to acquire the lock.

  • If the lock is not owned by any thread, set the state of the lock through the CAS function, and at the same time, set the current thread as the lock holder, and then return true.
  • If the holder of the lock is already the current thread, the status of the lock will be updated.
  • If the above two cases are not used, the attempt is considered to have failed.

Comparison of tryAcquire() between fair lock and unfair lock
Fair locks and unfair locks have different ways of trying to acquire locks.

When fair lock tries to acquire the lock, even if the lock is not held by any thread, it will determine whether it is the head of the CLH waiting queue. If it is, it will acquire the lock.

When unfair lock tries to acquire a lock, if the lock is not held by any thread, it will directly acquire the lock no matter where it is in the CLH queue.

Release unfair lock (based on JDK 11.0.5)

Unfair locks and fair locks have the same method and strategy for releasing locks.

The release mechanism has been introduced in Java-Multithreading-17-Fair-lock-Release, so I will not repeat here.

Summary

The difference between fair lock and unfair lock is the difference in the mechanism of acquiring a lock.

When trying to acquire a lock

  • Fair lock: the lock is only acquired when the current thread is the head of the CLH waiting queue
  • Unfair lock: as long as the current lock is idle, the lock is directly acquired, regardless of CLH Wait for the order in the queue. Only when the unfair lock attempts to acquire the lock fails, it will enter the CLH waiting queue and sort like the fair lock.