各种锁的关系以及其应用场景
互斥锁和自旋锁
最底层的两种是 互斥锁 和 自旋锁,有很多高级的锁都是基于它们实现的,可以认为它们是各种锁的基石。
他们两的区别是:
- 互斥锁加锁失败后,内核会将该线程置为「休眠」状态,等到锁被释放后,内核会在合适的时机唤醒线程
- 自旋锁加锁失败后,线程会一直轮训内核,查看锁的状态;如果锁是空闲的,将锁设置为当前线程持有
拿点外卖打比方,互斥锁会打电话问店老板外卖有没有做好,如果外卖没好,互斥锁不会再打电话,而是会等待店老板的回话,老板说好了再去拿。而自旋锁如果接到外卖没好的回答,则会一直打电话询问老板饭有没有做好,一直等到老板回答做好为止。
乐观锁与悲观锁
前面提到的互斥锁、自旋锁,都是属于悲观锁。
悲观锁做事比较悲观,它认为多线程同时修改共享资源的概率比较高,于是很容易出现冲突,所以访问共享资源前,先要上锁。
那相反的,如果多线程同时修改共享资源的概率比较低,就可以采用乐观锁。
乐观锁做事比较乐观,它假定冲突的概率很低,它的工作方式是:先修改完共享资源,再验证这段时间内有没有发生冲突,如果没有其他线程在修改资源,那么操作完成,如果发现有其他线程已经修改过这个资源,就放弃本次操作。
放弃后如何重试,这跟业务场景息息相关,虽然重试的成本很高,但是冲突的概率足够低的话,还是可以接受的。
可见,乐观锁的心态是,不管三七二十一,先改了资源再说。另外,你会发现乐观锁全程并没有加锁,所以它也叫无锁编程。
这里举一个场景例子:在线文档。
我们都知道在线文档可以同时多人编辑的,如果使用了悲观锁,那么只要有一个用户正在编辑文档,此时其他用户就无法打开相同的文档了,这用户体验当然不好了。
那实现多人同时编辑,实际上是用了乐观锁,它允许多个用户打开同一个文档进行编辑,编辑完提交之后才验证修改的内容是否有冲突。
还有我们常见的 SVN 和 Git 也是用了乐观锁的思想,先让用户编辑代码,然后提交的时候,通过版本号来判断是否产生了冲突,发生了冲突的地方,需要我们自己修改后,再重新提交。
乐观锁虽然去除了加锁解锁的操作,但是一旦发生冲突,重试的成本非常高,所以只有在冲突概率非常低,且加锁成本非常高的场景时,才考虑使用乐观锁。
简单总结就是:读的多,冲突几率小,乐观锁。写的多,冲突几率大,悲观锁。