多线程常见的锁策略

Posted 吞吞吐吐大魔王

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程常见的锁策略相关的知识,希望对你有一定的参考价值。

文章目录

1. 乐观锁 VS 悲观锁

锁策略说明实现
乐观锁认为锁冲突是小概率事件,假设数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。引入一个版本号,借助版本号识别出当前的数据访问是否冲突,如果冲突则将数据更新。
悲观锁认为锁冲突是大概率事件,总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。先加锁(比如借助操作系统提供的 mutex),获取到锁再操作数据们获取不到就等待。

乐观锁和悲观锁并无高下之分,主要还是看应用场景。

synchronized 既是一个乐观锁,又是一个悲观锁。能够根据实际场景,自动进行适应。

  • 当锁冲突概率比较低时,synchronized 内部就是以乐观锁策略实现的。
  • 当锁冲突概率比较高时,synchronized 内部就是以悲观锁策略实现的。

2. 读写锁

读写锁就是把读操作和写操作分别加锁,适用于读多写少的场景中,Java 标准库提供了 ReentrantReadWriteLock 类,该类实现了读写锁:

锁策略实现类补充
读锁ReentrantReadWriteLock.ReadLock这个对象提供了 lock/unlock 方法进行加锁和解锁。
写锁ReentrantReadWriteLock.WriteLock这个对象提供了 lock/unlock 方法进行加锁和解锁。

其中读锁和写锁之间的关系如下:

  • 读锁和读锁之间,不互斥
  • 写锁和写锁之间互斥
  • 读锁和写锁之间互斥

synchronized 不是读写锁。

3. 重量级锁 VS 轻量级锁

锁的核心特性原子性,追根溯源是 CPU 这样的硬件设备提供的:

  • CPU 提供了原子操作指令
  • 操作系统基于 CPU 的原子指令,实现了 mutex 互斥锁
  • JVM 基于操作系统提供的互斥锁,实现了 synchronized 和 ReetrantLock 等关键字和类来进行加锁。
锁策略说明特点
重量级锁加锁机制重度依赖了 OS 提供的 mutex涉及到了大量的内核态和用户态的切换,易引发线程的调度。
轻量级锁加锁机制尽可能不使用 mutex,而尽量在用户态代码完成,实在搞不定了才使用mutex很少会涉及内核态和用户态的切换,不容易引发线程调度
  • synchronized 开始是一个轻量级锁,如果锁冲突比较严重,就会变成重量级锁。
  • synchronized 内部的轻量级锁是基于自旋的方式实现的,重量级锁是基于挂起等待的方式实现的。

4. 自旋锁 VS 挂起等待锁

锁策略说明优点缺点
自旋锁如果获取锁失败,就会立即再次获取锁,循环下曲直到获取到锁为止没有放弃 CPU,不涉及到线程阻塞和调度,一旦锁被释放,就能第一时间获取到锁如果锁被其他线程持有的时间比较久,那就就会持续消耗 CPU 资源
挂起等待锁如果获取锁失败,就会挂起等待,会放弃 CPU,进入内核的阻塞队列,直到锁被释放且 CPU 调度到该线程节省 CPU 资源速度比较慢,一旦锁释放不能够第一时间直到
  • 自旋锁可以认为是轻量级锁的一种典型实现,也可以认为是乐观锁的一种典型实现。
  • 挂起等待锁可以认为是重量级锁的一种典型实现,也可以认为是悲观锁的一种典型实现。
  • sunchronized 开始是自旋锁,后面锁竞争激烈了就变成了挂起等待锁。

5. 公平锁 VS 非公平锁

如果有 A、B、C 三个线程,A 线程在获取到锁后,B 紧接着获取锁但失败了,C 最后也获取锁也失败了。当 A 释放锁后,如果按照先来后到的顺序获取锁,即 B 先获取锁,则是一个公平锁。如果不遵循先来后到,B 和 C 都有可能获取锁,则是一个非公平锁,

锁策略说明
公平锁遵循先来后多
非公平锁不遵循先来后到,随即调度
  • 操作系统内部的线程调度可以视为是随机的,如果不做额外的限制,锁就是非公平锁。如果要实现公平锁,则需要依赖额外的数据结构来记录线程的先后顺序。
  • synchronized 是非公平锁。

6. 可重入锁 VS 不可重入锁

锁策略说明实现
可重入锁允许同一个线程多次获取同一把锁在锁中记录锁持有的线程的身份,以及一个计数器(记录加锁的次数),如果当前加锁的线程就是持有锁的线程,则直接计数自增。
不可重入锁不允许同一个线程多次获取同一把锁
  • Java 里只要以 Reentrant 开头命名的锁就是可重入的,而且 JDK 提供的所有现成的 Lock 实现类,包括 synchronized 关键字都是可重入的。
  • Linux 系统提供的 mutex 是不可重入锁。
  • 不可重入锁会产生死锁操作(即同一线程加锁后,在没释放锁之前又进行加锁,那么此时由于是不可重入锁,该线程则会阻塞等待,但是线程又需要释放锁了才能解除当前状态,所以就会死锁)。
  • synchronized 是可重入锁。

以上是关于多线程常见的锁策略的主要内容,如果未能解决你的问题,请参考以下文章

多线程常见的锁策略

别翻了,常见的锁策略就在这里了~

多线程系列面试题4 - 常见的锁有哪些,使用过么,简单说说

多线程安全问题之锁策略,程序员一定要掌握的东西

多线程

多线程