关注△mikechen的互联网架构△,10年+BAT架构经验倾囊相授
大家好,我是 mikechen | 陈睿 。
Java面试经常问到自旋锁相关的问题,什么是自旋锁?Java如何实现自旋锁?自旋锁的底层原理是怎样的?下面一一详解@mikechen
什么是自旋锁?
自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
这种采用循环加锁,等待锁释放的机制就称为自旋锁(spinlock)。
自旋锁的优缺点
自旋锁的优点:
性能较高:自旋锁不会使线程状态切换,始终处于用户态,即线程始终处于活动状态,不会让线程进入阻塞状态,减少不必要的上下文切换,性能较高。
自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的,所以相比互斥锁来说,会快一些开销。
自旋锁的缺点
1)死锁:试图递归地获得自旋锁必然会引起死锁:递归程序的持有实例在第二个实例循环,以试图获得相同自旋锁时,不会释放此自旋锁。
2)在等待锁时进入循环会占用CPU,若等待的线程很多,对CPU的消耗会比较大。
3)不适合需要长时间等待的任务或线程,不适合大量线程等待的场景。
自旋锁的使用场景
1)等待时间比较短的任务中;
2)线程数量不太多的应用中;
3)当等待时间长或线程数量很大时,可以使用其他锁(比如:可重入锁)。
Java如何实现自旋锁?
我们来看一个Java实现的例子:
public class SpinLock {
private AtomicReference<Thread> cas = new AtomicReference<Thread>();
public void lock() {
Thread current = Thread.currentThread();
// 利用CAS
while (!cas.compareAndSet(null, current)) {
// DO nothing
}
}
public void unlock() {
Thread current = Thread.currentThread();
cas.compareAndSet(current, null);
}
}
lock获取锁的方法利用的CAS,当第一个线程A获取锁的时候,能够成功获取到,不会进入while循环,如果此时线程A没有释放锁,另一个线程B又来获取锁,此时由于不满足CAS,所以就会进入while循环,不断判断是否满足CAS,直到A线程调用unlock方法释放了该锁。
Java并发包中的很多类,都使用了CAS技术,是实现我们平时所说的自旋锁或乐观锁的核心操作。
CAS的算法
它的实现很简单,就是用一个预期的值和内存值进行比较,如果两个值相等,就用预期的值替换内存值,并返回 true。否则,返回 false。
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B
CAS的实现原理
如上图显示,线程1、线程2同时修改值。
如果通过CAS来实现,具体流程如下:
- 在内存地址V当中,存储着值为7的变量
- 线程1想要把变量的值增加1,对线程1来说,旧的预期值A=7,要修改的新值B=8
- 线程2抢先一步,把内存值V修改:8
- 线程1开始提交更新,首先对比了预期值A=7和实际值V的比较8(Compare),发现A不等于V的实际值,提交失败
以上,是自旋锁及Java实现自旋锁原理的详细解析,欢迎评论区留言交流或拓展。
我是 mikechen | 陈睿 ,关注【mikechen的互联网架构】,10年+BAT架构技术倾囊相授。
本文已同步我的技术博客 www.mikechen.cc,更新至我原创的《30W+字大厂架构技术合集》中。