目录
前言
锁策略主要是给锁的设计人员所进行参考,但是对于我们这些使用者而言,了解一些主要的锁策略可以帮助我们更正确地使用锁。锁策略在任何涉及到锁的相关知识都适用,并没有语言上的区分。
一、乐观锁和悲观锁
此处的" 乐观和悲观 "是针对于发生锁冲突概率而言;
如果发生锁冲突的概率比较小,那么就是乐观锁;
如果发生锁冲突的概率比较大,那么就是悲观锁。
对于乐观锁而言,由于发生锁冲突的概率较小,因此后续所需要做的工作也就更少,而悲观锁则反之。
二、轻量级锁和重量级锁
轻量级锁,其加锁的开销比较小,如消耗时间比较少、占用资源比较少等;
重量级锁,则加锁开销比较大,如消耗时间较多,占用资源较多。
这对锁策略和乐观悲观锁在概率上有着一定的重合:
由于轻量级开销较小,势必是因为所需工作较少,因此一个轻量级锁也很可能是一个乐观锁;
而重量级锁由于开销较大,那么所需工作肯定较多,因此一个重量级锁很可能是一个悲观锁。
但也只是存在一定的重合,并不绝对。以上两对锁策略更像是站在不同的角度来看待问题。乐观悲观锁是站在锁冲突概率的角度,考虑的是加锁之前的问题。而轻量级和重量级锁则是站在加锁的开销角度,考虑的是加锁之后的问题。
三、自旋锁和挂起等待锁
自旋锁是轻量级锁的一种典型实现,是在用户态下,使用自旋的方式来实现,例如使用 while 循环,不断地对锁的状态进行判断,目的是为了在锁释放后的第一时间内拿到该锁,进行加锁。
而挂起等待锁则是重量级锁的一种典型实现,当发现锁已经被占用了的时候,就由内核调度该线程挂起,也即是进入阻塞等待状态,到后续一定时间后再进行对锁状态的判断。
举一个鲜明的例子:
就好比追妹子,自旋锁就像一个死皮赖脸的舔狗一般,每天都会对已经有了男朋友的女神嘘寒问暖,早安午安晚安,为的就是能在女神分手后的第一时间趁虚而入,捕获女神的芳心;
而挂起等待锁,就不像自旋锁那么舔,像一个追求者,在得知女神已经有了男朋友了之后,也就不再打扰对方,而是干自己的事情去,等到偶然得知女神分手了之后,再次出击发起进攻。
那么就很容易发现,舔狗的方式虽然比较耗费精力一些,但是可以以最快的速度在女神分手之后就发起攻击。普通追求者的方式虽然知道消息的速度没那么快,但是就没那么耗费时间和精力。
因此,类比到锁策略上也是如此,自旋锁由于一直发起" 询问 ",因此获取到锁的速度最快,但是就比较耗费 cpu 资源,挂起等待锁就相反,虽然消耗的 cpu 资源比较少,但也就无法保证第一时间拿到锁。
四、读写锁和互斥锁
互斥锁就是普通的锁,如 synchronized;
读写锁,是一种读数据和写数据分别加锁的锁,也即是把读操作加锁和写操作加锁分开了。
这么分开就有一个好处:
由于多线程中,多个线程同时读取同一个数据,并不会造成线程安全问题,因此读写锁就有以下设定:
1)如果两个线程,一个读加锁,另一个也读加锁,不会产生锁竞争;
2)如果两个线程,一个读加锁,另一个写加锁,会产生锁竞争;
3)如果两个线程,一个写加锁,另一个也是写加锁,会产生锁竞争。
一般的锁,是读写一起加锁,就会导致两个线程同时读的时候,也会产生锁竞争。这一个小小的区别,就会很大程度影响到程序的效率,因为在日常使用中,读操作是一个很频繁进行的操作,而如果每次都产生锁竞争,阻塞等待的时间就会很影响效率。
五、公平锁和非公平锁
此处的公平和不公平是针对于锁释放之后,是否按照等待的先后顺序来竞争锁。
当锁释放之后,如果按照阻塞等待的先后顺序,让先进入阻塞等待的线程先获得锁,那么就是公平锁;如果锁释放之后,所有线程都有一样的概率来竞争锁,那么则为非公平锁。通俗点讲,就是遵守先来后到的是公平锁,否则则为非公平锁。
举个栗子:
还是那个追求者们心心念念的女神,如果女神分手之后,已经早早等候在她身边的舔狗可以得到女神的认可,那么就相当于给女神" 加锁 ",就是公平锁;
如果是女神说:所有追求者公平竞争,那么就是非公平锁。
六、可重入锁和不可重入锁
可重入锁是指:一个线程,针对同一把锁,连续加锁两次,产生了死锁,那么就是不可重入锁,如果不会产生死锁,那么就是可重入锁。
可重入锁内部,会记录当前是哪个线程持有了该锁,因此当该锁连续加锁两次时,就能判别出是已经持有该锁的线程再次加锁,因此就不会产生死锁。
并且,还有记录持有该锁的线程加了几次锁的" 计数器 ",当该线程加锁时,计数器就 +1,释放锁就 -1,这样就方便了后续解锁的时候,判别什么时候才应该真正的解锁。
下面就是可重入锁的示意图( synchronized 本身就是一个可重入锁 ):