面试常问 乐观锁 & 悲观锁 、自旋锁 & 互斥锁 ?诸君听我一言

文章目录

    • 乐观锁
      • 实现
    • 悲观锁
      • 实现
    • 乐观锁 VS 悲观锁
    • 自旋锁 & 互斥锁
    • 长尾流量优化

乐观锁和悲观锁并不是一种真实存在的锁,而是一种设计思想,乐观锁和悲观锁对于理解后端多线程和数据库来说至关重要,那么本篇文章就来详细探讨一下这两种锁的概念以及实现方式。

我就不喜欢看人说什么“Java的乐观锁和悲观锁”,思想还要分是谁是谁的嘛。。。

乐观锁

面试常问 乐观锁 & 悲观锁 、自旋锁 & 互斥锁 ?诸君听我一言_第1张图片
乐观锁,你看它名字就知道,把事情想得很单纯,它总认为资源和数据不会被别人所修改,所以读取不会上锁,但是乐观锁在进行写入操作的时候会判断当前数据是否被修改过。可以使用版本号等机制。

乐观锁多适用于多度的应用类型,这样可以提高吞吐量。

实现

  1. 使用自增长的整数表示数据版本号。
    面试常问 乐观锁 & 悲观锁 、自旋锁 & 互斥锁 ?诸君听我一言_第2张图片
    如果这双写互不干扰,男的取出,版本号为0,男的写入,版本号+1;女的取出,版本号为1,女的写入,版本号为2。
    如果这双写相互干扰了,男的取出,版本号为0;男的还没写入,女的取出,版本号为0;男的写入,版本号为1;女的写入,发现版本号不匹配,则写入失败,应该重新读取金额数和版本号。

  2. 使用时间戳来实现.

悲观锁

面试常问 乐观锁 & 悲观锁 、自旋锁 & 互斥锁 ?诸君听我一言_第3张图片

悲观锁是一种悲观思想,它总认为最坏的情况可能会出现,它认为数据很可能会被其他人所修改,所以悲观锁在持有数据的时候总会把资源 或者 数据 锁住,这样其他线程想要请求这个资源的时候就会阻塞,直到等到悲观锁把资源释放为止。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。悲观锁的实现往往依靠数据库本身的锁功能实现。

实现

可以使用数据库的锁机制。

乐观锁 VS 悲观锁

只能说,各有千秋吧。

乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。

悲观锁会造成访问数据库时间较长,并发性不好,特别是长事务。
乐观锁在现实中使用得较多。

上面那个例子放在这里就变成了:一方拿到锁之后,另一方就等着,知道一方将锁释放,另一方继续操作。


自旋锁 & 互斥锁

自旋锁和互斥锁嘛,一直在用的,不过以前只是简单的叫它们:锁。原来人家有名字的啊。

wait() 晓得不?timewait()晓得不?

互斥锁:阻塞等待
自旋锁:等两下就去问一声:好了不?我很急啊!好了不?你快点啊。。。哈哈哈哈哈

自旋锁的原理比较简单,如果持有锁的线程能在短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞状态,它们只需要等一等(自旋),等到持有锁的线程释放锁之后即可获取,这样就避免了用户进程和内核切换的消耗。

因为自旋锁避免了操作系统进程调度和线程切换,所以自旋锁通常适用在时间比较短的情况下。由于这个原因,操作系统的内核经常使用自旋锁。但是,如果长时间上锁的话,自旋锁会非常耗费性能,它阻止了其他线程的运行和调度。线程持有锁的时间越长,则持有该锁的线程将被 OS(Operating System) 调度程序中断的风险越大。如果发生中断情况,那么其他线程将保持旋转状态(反复尝试获取锁),而持有该锁的线程并不打算释放锁,这样导致的是结果是无限期推迟,直到持有锁的线程可以完成并释放它为止。

解决上面这种情况一个很好的方式是给自旋锁设定一个自旋时间,等时间一到立即释放自旋锁。适应性自旋锁意味着自旋时间不是固定的了,而是由前一次在同一个锁上的自旋时间以及锁拥有的状态来决定,基本认为一个线程上下文切换的时间是最佳的一个时间。


长尾流量优化

刷一下就过去了,确定不收藏一下吗?

你们长得这么好看,不顺手把关注点上吗?嘿嘿
面试常问 乐观锁 & 悲观锁 、自旋锁 & 互斥锁 ?诸君听我一言_第4张图片
面试常问 乐观锁 & 悲观锁 、自旋锁 & 互斥锁 ?诸君听我一言_第5张图片

你可能感兴趣的:(Linux服务器编程,多线程,linux,数据库,操作系统)