自旋锁(spinlock)

自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少上下文切换的消耗,缺点是循环会消耗CPU

即:是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环

在CAS中的Unsafe类中就要自旋锁的应用,如下源码:
该循环会一直去尝试获得它的期望值,然后去比较并交换

public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

自旋锁的实现

利用CAS算法的思想直接实现一个自旋锁:

/**
 * 自旋锁的实现
 */
public class Spinlock {
    private AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void myLock() {
        // 获取当前线程
        Thread thread = Thread.currentThread();
        System.out.println("线程" + thread.getName() + "进入了myLock");
        // 获取锁,利用CAS算法,如果没有获取到期望值,则就一直在这循环,直到获取到期望值为止,自旋锁的核心
        while (!atomicReference.compareAndSet(null, thread)) {
        }
    }

    public void myUnLock() {
        Thread thread = Thread.currentThread();
        System.out.println("线程" + thread.getName() + "进入了myUnLock");
        atomicReference.compareAndSet(thread, null);
    }

测试类:

public class SpinLockTest {
    Spinlock spinlock = new Spinlock();

    public void test() {
        System.out.println(Thread.currentThread().getName() + "进入了test");
        try {
            spinlock.myLock();
            System.out.println(Thread.currentThread().getName() + " 获取到了锁");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } finally {
            spinlock.myUnLock();
            System.out.println(Thread.currentThread().getName() + " 释放了锁");
        }
    }

    public static void main(String[] args) {
        SpinLockTest spinLockTest = new SpinLockTest();
        new Thread(new Runnable() {
            @Override
            public void run() {
                spinLockTest.test();
            }
        }, "t1").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                spinLockTest.test();
            }
        }, "t2").start();
    }
}

运行结果:

t1进入了test
线程t1进入了myLock
t1 获取到了锁
t2进入了test
线程t2进入了myLock
线程t1进入了myUnLock
t1 释放了锁
t2 获取到了锁
线程t2进入了myUnLock
t2 释放了锁

分析代码和结果:
myLock()方法利用的CAS,当第一个线程t1获取锁的时候,能够成功获取到,不会进入while循环,如果此时线程t1没有释放锁,另一个线程t2又来获取锁,此时由于不满足CAS,所以就会进入while循环,不断判断是否满足CAS,直到t1线程调用unlock方法释放了该锁

自旋锁存在的问题

  1. 如果某个线程持有锁的时间过长,就会导致其它等待获取锁的线程进入循环等待,消耗CPU。使用不当会造成CPU使用率极高。
  2. 上面Java实现的自旋锁不是公平的,即无法满足等待时间最长的线程优先获取锁。不公平的锁就会存在“线程饥饿”问题。

自旋锁的优点

  1. 自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快
  2. 非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换。 (线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能)