自旋锁(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方法释放了该锁
自旋锁存在的问题
- 如果某个线程持有锁的时间过长,就会导致其它等待获取锁的线程进入循环等待,消耗CPU。使用不当会造成CPU使用率极高。
- 上面Java实现的自旋锁不是公平的,即无法满足等待时间最长的线程优先获取锁。不公平的锁就会存在“线程饥饿”问题。
自旋锁的优点
- 自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快
- 非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换。 (线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能)