27 What Is the Spin Lock What Are the Benefits and Consequences of Self Spinning

27 What Is the SpinLock What Are the Benefits and Consequences of Self-Spinning #

In this lesson, we will mainly explain what a spin lock is and what are the benefits and consequences of using a spin lock.

What is spinning? #

First, let’s understand what spinning means. “Spinning” can be understood as “self-rotation”, where “rotation” refers to “looping”, such as a while loop or a for loop. “Spinning” means continuously looping here until the goal is achieved. Unlike ordinary locks, if the lock cannot be obtained, it will not enter a blocking state.

Comparison of the process of acquiring a lock with and without spinning #

Next, let’s use this flowchart to compare the process of acquiring a lock with and without spinning.

img

First, let’s look at the spin lock. It does not give up CPU time slices, but waits for the lock to be released by spinning, which means it will keep trying to acquire the lock again and again until it succeeds.

Now let’s look at the non-spinning lock. The non-spinning lock is completely different from the spin lock. If it cannot acquire the lock at a given time, it switches its thread state and puts the thread to sleep, allowing the CPU to do many other things in the meantime until the thread that previously held the lock releases it. The CPU then resumes the previous thread and lets it try to acquire the lock again. If it fails again, it puts the thread back to sleep. If it succeeds, it can successfully acquire the lock for the synchronized resource.

As we can see, the biggest difference between the non-spinning lock and the spin lock is that if it cannot obtain the lock, it blocks the thread until it is awakened, while the spin lock keeps trying. So, what are the benefits of this continuous spinning of the spin lock?

Benefits of spin locks #

First, blocking and waking up threads are both expensive operations. If the content inside the synchronized code block is not complex, the overhead of switching threads may be greater than the overhead of executing the actual business code.

In many scenarios, the content inside our synchronized code block may not be much, so the required execution time is also short. If we switch the thread state just for this short time, it is actually better to let the thread not switch states but spin to try to acquire the lock, waiting for other threads to release the lock. Sometimes, we just need to wait for a while to avoid the overhead of context switching and increase efficiency.

To summarize the benefits of spin locks in one sentence, spin locks use loops to continuously try to acquire the lock, keeping the thread in the Runnable state and saving the overhead of thread state switching.

Implementation of AtomicLong #

In the java.util.concurrent package, which is available in Java 1.5 and above, most of the atomic classes are implemented using spin locks.

For example, let’s take a look at the implementation of AtomicLong. It has a getAndIncrement method, whose source code is as follows:

public final long getAndIncrement() {
    return unsafe.getAndAddLong(this, valueOffset, 1L);
}

As we can see, it invokes the unsafe.getAndAddLong method, so let’s take a look at this method:

public final long getAndAddLong(Object var1, long var2, long var4) {
    long var6;
    do {
        var6 = this.getLongVolatile(var1, var2);
    }
    while (!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
    return var6;
}

In this method, a do-while loop is used. It is evident here:

do {
    var6 = this.getLongVolatile(var1, var2);
} 
while (!this.compareAndSwapLong(var1, var2, var6, var6 + var4));

This do-while loop is a spinning operation. If there is a situation where the modification is not successful due to competition from other threads during the modification process, it will enter an infinite loop in the while statement until the modification is successful.

Implementing a reentrant spin lock #

Now let’s take a look at an implementation of a reentrant spin lock.

The code is as follows:

package lesson27;

import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;

/**
 * Description: Implementation of a reentrant spin lock
 */
public class ReentrantSpinLock {
    private AtomicReference<Thread> owner = new AtomicReference<>();
    // Reentrant count
    private int count = 0;

    public void lock() {
        Thread t = Thread.currentThread();
        if (t == owner.get()) {
            ++count;
            return;
        }
        // Spin to acquire the lock
        while (!owner.compareAndSet(null, t)) {
System.out.println("Spinning");

}

}

public void unlock() {

Thread t = Thread.currentThread();

// Only the thread holding the lock can unlock it

if (t == owner.get()) {

if (count > 0) {

--count;

} else {

// No need for CAS operation here, as there is no competition, only the thread holder can unlock it

owner.set(null);

}

}

}

public static void main(String[] args) {

ReentrantSpinLock spinLock = new ReentrantSpinLock();

Runnable runnable = new Runnable() {

@Override

public void run() {

System.out.println(Thread.currentThread().getName() + " starts trying to obtain the spin lock");

spinLock.lock();

try {

System.out.println(Thread.currentThread().getName() + " obtained the spin lock");

Thread.sleep(4000);

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

spinLock.unlock();

System.out.println(Thread.currentThread().getName() + " released the spin lock");

}

}

};

Thread thread1 = new Thread(runnable);

Thread thread2 = new Thread(runnable);

thread1.start();

thread2.start();

}

}

The output of this code is:

...
Spinning
Spinning
Spinning
Spinning
Spinning
Spinning
Spinning
Spinning
Thread-0 released the spin lock
Thread-1 obtained the spin lock

Printing “Spinning” multiple times in the beginning indicates that the CPU is still running during the spin.

Drawbacks #

So does the spin lock have any drawbacks? Yes, it does. Its biggest drawback is that although it avoids the overhead of thread context switching, it brings its own overhead because it needs to keep trying to acquire the lock. If the lock is never released, this kind of spinning is just useless and will waste processor resources. In other words, although the initial overhead of spin lock is lower than that of thread context switching, as time goes on, this overhead will increase and may even exceed the overhead of thread context switching, becoming a case of diminishing returns.

Use Cases #

So let’s take a look at the scenarios where spin locks are appropriate. First of all, spin locks are suitable for scenarios where concurrency is not very high and the critical section is relatively short. In this way, we can improve efficiency by avoiding thread context switching.

However, if the critical section is large and the thread holds the lock for a long time before releasing it, it is not appropriate to use spin locks because spinning will occupy the CPU but will not be able to acquire the lock, resulting in wasted resources.