Java面试经常问到自旋锁相关的问题,什么是自旋锁?Java如何实现自旋锁?自旋锁的底层原理是怎样的?下面一一详解@mikechen
什么是自旋锁?
自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
这种采用循环加锁,等待锁释放的机制就称为自旋锁(spinlock)。
自旋锁的优缺点
自旋锁的优点:
性能较高:自旋锁不会使线程状态切换,始终处于用户态,即线程始终处于活动状态,不会让线程进入阻塞状态,减少不必要的上下文切换,性能较高。
自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的,所以相比互斥锁来说,会快一些开销。
自旋锁的缺点
1)死锁:试图递归地获得自旋锁必然会引起死锁:递归程序的持有实例在第二个实例循环,以试图获得相同自旋锁时,不会释放此自旋锁。
2)在等待锁时进入循环会占用CPU,若等待的线程很多,对CPU的消耗会比较大。
2)不适合需要长时间等待的任务或线程,不适合大量线程等待的场景。
自旋锁的使用场景
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的实际值,提交失败
关于CAS,可以查看《CAS实现原理详解》这篇文章。
陈睿mikechen
10年+大厂架构经验,资深技术专家,就职于阿里巴巴、淘宝、百度等一线互联网大厂。
关注「mikechen」公众号,获取更多技术干货!
后台回复【面试】即可获取《史上最全阿里Java面试题总结》,后台回复【架构】,即可获取《阿里架构师进阶专题全部合集》