目录
一、常见锁策略
1.1乐观锁 and 悲观锁
1.2读写锁 and 普通互斥锁
1.3重量级锁 and 轻量级锁
1.4自旋锁 and 挂起等待锁
1.5公平锁 and 非公平锁
1.6可重入锁 and 不可重入锁
1.7信号量Semaphore
二、相关经典面试题
2.1你是怎么理解乐观锁和悲观锁的,具体怎么实现呢?
2.2介绍一下读写锁?
2.3什么是自旋锁,为什么要使用自旋锁策略呢?
2.4synchronized 是可重入锁么?
乐观锁:
总是假设数据大多数情况下不会发生冲突,只有当数据正式提交更新的时候,若发现了数据冲突,才会进行将冲突的信息反馈给用户,让用户决定如何去解决;总的来说:乐观就是指锁冲突的概率不高,因此做的工作就可以简单一些,因此性能也比较高,但往往不能处理到所有问题,需要一定的系统复杂度
举个栗子(乐观):临近考试,我就比较淡定,对于有些题我就觉得,上课既然讲的也很少,因该也就很少考,所以就不去复习这些题了;另一方面,我对自己平时的学习很自信,因此,也就没有必要去复习了;
使用场景:适用于读操作多,写操作少的场景.
悲观锁:
总是假设最坏的情况,觉得数据大多数情况下都会发生冲突,所以再每次拿数据的时候都会给数据上锁,这样就算别人想拿到这个数据,也需要阻塞等待直到解锁;总的来说:预测锁冲突的概率较高,因此做的工作就要复杂一些,因此性能不高,但比较安全,synchronized就是一个典型的悲观锁;
举个栗子(悲观):临近考试,我就比较慌,总感觉什么题都会考到,让我辗转难眠,于是我就多去复习,什么都想复习一下,总担心哪里复习不到;
使用场景:适用于读操作少,写操作多的场景.
读写锁:
咱们清楚的一点是:读锁于读锁之间,不会产生竞争,写锁和写锁以及读锁和写锁之间,存在竞争,因此,读写锁分为两种,一种是写锁(只能有一个线程拥有),一种是读锁(可以有多个线程拥有),这是为了在执行读写操作的时候要额外表明读写意图,读者与读者之间并不互斥,而写者要求于任何人互斥;
于是读写锁相比于普通互斥锁就少了很多竞争;那什么是普通互斥锁呢?往下看!
普通互斥所:
这就不同于读写锁有那么多要求,普通互斥锁就如同synchronized的一样,当两个线程竞争同一把锁,就会阻塞等待;
重量级锁:
加锁的开销比较大,为什么呢?,大量的内核态用户态的切换,很容易引发线程调度,因此开销是比较大的;
轻量级锁:
加锁机制尽可能不通过系统调度来进行用户态和内核态的切换,而是尽量在用户态完成,实在不行,再切换;也就是说,可以说是典型的纯用户态的加锁逻辑,开销较小;
思考:synchronized刚开始是一个轻量级锁,但是一旦所冲突比较严重时,就变成了重量级锁;
自旋锁:
是一个典型的轻量级锁,可以简单的理解为“忙等”,消耗大量的CPU反复询问当前锁是否就绪;(只要拿不到锁,就死等);缺点很明显,就是消耗大量的CPU资源,做无用功;优点是可以第一时间拿到锁;
挂起等待锁:
是一个典型的重量极锁,可以简单的理解为“不忙等”,进入等待状态时,让出CPU资源,使其可以进行一些其他有意义的事情;优点很明显,合理利用CPU资源提高效率;缺点就是可能不那么及时拿到锁;
举个栗子:想象这样一个场景,挂起等待锁和自旋锁同时追一个妹子;
挂起等待锁:这个妹子给他说,这阵子我有点忙,过一段时间咱们再一起去看电影;挂起等待锁就很好的利用在这段时间里努力学习提升自己,然后当妹子给他打电话的时候,他还在废寝忘食的学,以至于忘记接电话,那这个妹子可能就要跑路喽~
自旋锁:整天死皮赖脸的给这个妹子发消息,早安晚安...经常打电话,这个女生就像被监视了一样,无论发生什么情况,这个自旋锁肯定能第一时间知道;
公平锁:
锁获得的顺序与线程方法先后顺序保持一致,优点便是执行的时候,顺序是可知的;
非公平锁:
锁的获取顺序与线程方法的先后顺序无关,优点就是性能很高;
注意:再没有任何额外限制的时候,锁就是非公平的,若要公平,需要额外提供数据结构,记录线程的先后顺序,公平锁与非公平锁之间也没有好坏之分,具体看应用场景;synchronized就是非公平锁;
简单来说:
可重入锁:同一个线程针对同一把锁,连续加锁两次,不会死锁;
不可重入锁:同一个线程针对同一把锁,连续加锁两次,会死锁;
这个之前博主专门写过文章,可以来看看~
http://t.csdn.cn/PjjkZ
信号量本身是一个计数器,表示可用资源数,当计数器器为0的时候,再申请资源,就会产生阻塞,直到有线程释放资源为止;另外,锁可以视为一个特殊的信号量(可用资源数为1),信号量可以视为一个更广义的锁;
基本操作有两个:(注意:P,V为荷兰语缩写)
P操作(acquire() 方法):申请一个资源,可用资源 - 1;
V操作(release() 方法):释放一个资源,可用资源 + 1;
Semaphore semaphore = new Semaphore(//初始资源数(阈值))
怎么理解乐观锁和悲观锁,上面的概念已经很清楚了,这里提一下具体实现是什么:
悲观锁的实现就是先加锁(比如借助操作系统提供的 mutex), 获取到锁再操作数据. 获取不到锁就 等待.
乐观锁的实现可以引入一个版本号. 借助版本号识别出当前的数据访问是否冲突.
具体介绍,上面已经很清楚了,忘了的话,记得往上翻翻;
问道为什么要使用自旋策略,实际上就是再问你,自旋锁的优点是什么,最好把缺点也说一下,面试官一天要面试很多人,而面试官实际上和你是站在一方的,就是一个让你展现自我的地方,所以,面试官自然是希望多说点你了解的
是可重入锁,是通过一个计数器来记录持有锁的线程的身份,发现锁是当前锁,计数器直接++;具体可以看一下博主整理出的这篇文章:http://t.csdn.cn/93QQI
上面这条链接详细的介绍了可重入锁的底层逻辑