多线程常见的锁策略

这篇具有很好参考价值的文章主要介绍了多线程常见的锁策略。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

1.1 乐观锁 和 悲观锁

1.2 轻量级锁 和 重量级锁

1.3 自旋锁 和 挂起等待锁

1.4 互斥锁 和 读写锁

1.5 可重入锁 和 不可重入锁

1.6 公平锁 和 非公平锁

1.7 synchronized 锁的属性


一、锁策略

说到锁,Java 里面常用的锁有 synchronized ,锁的意义在于在多线程并发执行的时候保证线程安全,防止出现 BUG, 造成线程不安全的原因本质上是因为线程的调度是无序的,Java本身不负责调度,是由操作系统按照一定的规则来调度线程,CPU 并发执行,但是由于其他应用程序无法干预系统线程的调度,所以可以认为任务线程的调度是无序的。

锁策略就是设计一种解决线程安全问题的办法,在程序设计中根据业务需求需要去实现一个锁,基本上就是参考常见的锁策略。


1.1 乐观锁 和 悲观锁

顾名思义:

乐观锁,用来预测接下来的锁冲突概率不大,所谓锁冲突就是多线程针对同一对象加锁,同一时间内只有一个线程可以获取到锁并执行,其他线程阻塞等待锁释放。

悲观锁,用来预测接下来的锁冲突概率很大,这就涉及到程序需要不断的加锁、解锁,所以通常来讲悲观锁纯纯的打工仔,乐观锁工作的机会是比较少的,但是也不绝对。

悲观者总是喜欢将一件事往最坏的情况下预测,并且已经做好了最坏的打算,所以啊即使当那一刻真的来临,可能也会庆幸意料之中吧。

乐观者也许是珍惜当下的心态,每一天开开心心的就好了,即使遇到很不好的事情也能很快释然吧


1.2 轻量级锁 和 重量级锁

轻量级锁,即:加锁解锁的过程更快更高效。

常常使用在不同的线程交替的执行同步块中的代码(同步代码块指在代码块前加上 synchronized关键字的代码块),这种情况下,用重量级锁是没必要的。轻量级锁所适应的场景是线程交替执行同步块的场合,锁冲突较少的情况,如果出现多线程同时竞争同一把锁,锁冲突严重,那么就会导致轻量级锁膨胀为重量级锁。锁的强度对线程的状态的影响也是不同的。

重量级锁,即 :加锁解锁的过程更慢更低效

运用的场景那必然是多线程同时竞争同一对象锁,锁冲突严重,其他线程阻塞等待锁释放,当对象锁释放的时候,因锁竞争而阻塞等待的线程就会被唤醒,继续竞争锁,没有抢到的线程又会继续阻塞等待,如此以往,对资源还是有一定的消耗的。

线程阻塞等待即:该线程暂时不参与系统的调度,也就不会被 CPU 执行了。


1.3 自旋锁 和 挂起等待锁

轻量级锁基于自旋锁的一种实现,当多线程对同一对象锁进行竞争的时候,就会造成锁冲突,但是如果这种冲突并不严重的话,比如两个线程竞争,不是你就是我的,那没竞争到锁的另一个线程此时不会 “阻塞等待 ”,而是不断的循环尝试获取锁,当然这也会浪费CPU 的执行资源(也就是忙等状态),但是当锁释放的时候,自旋等待的线程能够第一时间获取到锁

重量级锁是基于挂起等待锁的一种实现,也就是锁冲突比较严重的状态,例如:有10 个线程竞争同一把对象锁,同一单位时间内只有一个线程能拿到锁并执行,那如果 10 个线程都自旋等待那就是得不偿失的,CPU 还得是并发的执行他们,让他们一直保持随时获取锁的状态,这消耗是得不偿失的,所以当锁冲突比较严重的时候直接让等待获取锁状态的线程进入阻塞等待,暂时不参与CPU的调度执行, 虽然会有唤醒线程,带来的开销,但是相较于一直并发执行这些线程,就大大降低了CPU 的开销。CPU 每秒钟可执行几十亿条指令。

多线程常见的锁策略

基准速度:1 GHz 等于 一秒10 亿条指令。


 针对以上三种锁策略: synchronized 这把锁具有那些属性呢?

synchronized 既是悲观锁,也是乐观锁,既是轻量级锁,也是重量级锁,轻量级锁基于自旋锁实现,重量级锁基于挂起等待锁实现。

synchronized 会根据当前锁竞争的程度,自适应的转化锁属性,如果锁冲突不激烈,是以乐观锁或乐观锁的状态运行,如果锁冲突激烈,以悲观锁或重量级锁的状态运行。


1.4 互斥锁 和 读写锁

互斥锁:

在多线程编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。  

多线程对同一对象(synchronized 修饰的代码块)进行操作就会发生锁竞争,竞争同一把对象锁,那么同一单位时间内只有一个线程能拿到锁并执行,此时其他线程就 “阻塞等待”,线程进入 synchronized 修饰的代码块就意味着获取到锁了,然后加锁,出代码块释放锁,解锁。实现一种互斥访问,不是你执行就是我执行,不管谁先进入,其他人都不能继续执行了。

读写锁:

顾名思义就是对读、写操作加锁。

在线程的环境下如果多个线程同时读数据不会有线程安全问题,如果多线程允许同时对一个变量的值进行修改,那完了,你改我也改(例如:对同一变量进行自增操作,两个线程同时读取的数据是一样的,但是如果写的时候线程1 先调度执行,修改完毕,写回内存,然后线程2 调度执行,修改完毕写回内存,那线程 2 修改后的值就会把线程 1 的值覆盖掉)很可能就对数据的有效性造成重大的影响。

所以读写锁的属性:

1. 读锁和读锁之间不会产生锁竞争

2. 写锁和写锁之间有锁竞争,同一单位时间内只有一个线程能拿到锁并执行写操作

3. 读锁和写锁之间也会有锁竞争,同一单位时间内只有一个线程能拿到锁并执行读操作,然后执行写操作

 Java 标准库中提供了两个专门的读写锁,读写锁是使用 ReentrantReadWriteLock 类来实现的

  • ReentrantReadWriteLock.ReadLock 表示读锁,它提供了 lock 方法进行加锁、unlock 方法进行解锁。
  • ReentrantReadWriteLock.WriteLock 表示写锁,它提供了 lock 方法进行加锁、unlock 方法进行解锁。

1.5 可重入锁 和 不可重入锁

如果一个线程执行 synchronized 修饰的代码块,意味着该线程获取到了对象锁,其他线程因无法获取对象锁而进入阻塞等待,等待锁释放,此时,如果该线程又执行到了 synchronized 修饰的代码块 ,是两个代码块嵌套的情况,而且使用的是同一个对象锁,概念是,一个对象只有一把锁,那么线程刚进入  synchronized 的代码块就获取了这把锁,此时又遇到需要获取该对象锁的情况,那么线程就有两种情况:

1. 骂骂咧咧的说谁把锁抢走了,气死我了,进入阻塞等待(死锁),因为谁拿到锁,就由谁释放锁,线程进入 synchronized 修饰的代码块获取锁,出 synchronized 修饰的代码块就会释放锁,这个时候别的线程就可以获取锁,现在这种情况是线程手里拿着锁,还没释放锁,又遇到需要获取该对象锁的情况,那他就会阻塞等待锁释放,产生了死锁问题,这就叫做 不可重入锁。

2. 由于线程手上有该对象锁,所以再遇到需要获取该对象锁的情况就直接验证一下是否是该锁的拥有者,如果是则允许进入,不会产生死锁问题,这就是是可重入锁。

Object locker = new Object(); // 使用第三方对象锁

synchronized(locker) {  //线程获取 locker 对象锁

   //线程再次获取 locker 对象锁,此时锁已经被线程获取了还未释放,就会有两种结果

   synchronized(locker) {

   }

}

概念有些晦涩难懂,接下来给大家举一些例子来讲述:

不知道大家有有没有在日常生活中偶尔遇到找某件东西怎么都找不到,找了一圈又一圈最后发现要找的东西就在自己手里,害。

多线程常见的锁策略


在单线程的情况下,在某些特定的情况下可重入锁就非常好使,不会产生死锁问题。

但是如果是多个线程多把锁的情况下,即使是可重入锁,也会死锁~

多线程常见的锁策略

 综上所述:可重入锁只有在单线程的状态下才好用,多线程就需要注意加锁顺序了。

多线程常见的锁策略

 死锁的四个必要条件:

1. 互斥使用,一个线程拿到一把锁之后,另一个线程阻塞等待锁释放(张三上厕所,李四等待)

2. 不可抢占,一个线程拿到锁,只能自己主动释放,不能被其他线程强行占有。

3. 请求和保存状态,张三手里拿着对象锁,还一直尝试获取锁。

4. 循环等待,“家里的要是锁车里了,车钥匙锁家里了”,获取锁的逻辑是循环的,无法破局。


1.6 公平锁 和 非公平锁

因为同一单位时间内只有一个线程能获取到锁并执行,其他竞争同一对象锁的线程进入阻塞等待的状态,此时如果又加入了几个线程来竞争对象锁,当对象锁释放的时候,这些线程之间获取到对象锁的概率是均等的,没有先来后到之理,这叫做 ——非公平锁。

反之,如果锁竞争的线程遵守先来后到,当对象锁释放的时候,最先尝试获取对象锁失败进入等待状态的线程最先获取,那么这就叫做——公平锁。

这些组锁概念还是很容易理解的,画个图理解一下:

多线程常见的锁策略 ​编辑

 Java 中提供的 synchronized 属于非公平锁,如果想要实现公平锁,可以在 synchronized 的基础上使用队列来记录这些线程任务,put  执行。


1.7 synchronized 锁的属性

synchronized 锁的特点:

1. 既是乐观锁,也是悲观锁

2. 既是轻量级锁,也是重量级锁

3. 轻量级锁是基于自旋锁实现,重量级锁是基于挂起等待锁实现

4. 不是读写锁,是互斥锁

5. 是可重入锁

6. 是非公平锁

以上锁属性上文都有解析。


人终将被年少不可得之物困其一生,也终会因一事一景解开一生困惑。文章来源地址https://www.toymoban.com/news/detail-425221.html

到了这里,关于多线程常见的锁策略的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 悲观锁和乐观锁(易懂)

    这里可以把悲观锁看作悲观的人,啥事都往最坏的方向想。乐观锁看作乐观的人,啥事都往最好的方向想。 首先,说一下悲观锁。 悲观锁就是假设并发情况下一定会有其他线程来修改数据,因此在处理数据之前,先将数据锁住,确保其他线程不能进行修改 。感觉像一个过于

    2024年02月08日
    浏览(36)
  • 悲观锁和乐观锁、缓存

    悲观锁: 悲观锁的实现通常依赖于数据库提供的机制,在整个处理的过程中数据处于锁定状态,session的load方法有一个重载方法,该重载方法的第三个参数可以设置锁模式,load(object.class , int id,LockMode.?),该方法的?就是具体的锁模式。 乐观锁: 乐观锁使用版本号或者时间戳

    2024年02月09日
    浏览(41)
  • [锁]:乐观锁与悲观锁

    摘要:乐观锁;悲观锁;实现方法;本地锁;分布式锁;死锁;行级锁;表级锁 问题 : ① 在多个线程访问共享资源时,会发生线程安全问题,例如:在根据订单号生成订单时,若用户第一次由于某种原因(网络连接不稳定)请求失败,则会再次发生请求,此时便会产生同一

    2024年02月08日
    浏览(35)
  • django实现悲观锁乐观锁

    前期准备 1.原生mysql悲观锁 2.orm实现上述(悲观锁)  3 乐观锁秒杀--》库存还有,有的人就没成功  

    2024年02月12日
    浏览(40)
  • 什么是乐观锁和悲观锁?

    乐观锁和悲观锁是并发控制的两种不同策略,用于在多线程环境下管理共享资源的访问。它们有不同的思想和实现方式: 悲观锁(Pessimistic Locking) : 思想 :悲观锁的思想是,它假定在并发访问中会发生冲突,因此在访问共享资源之前会先加锁,以防止其他线程访问。悲观

    2024年02月10日
    浏览(37)
  • [锁]:乐观锁、悲观锁与死锁

    摘要:乐观锁;悲观锁;实现方法;本地锁;分布式锁;死锁;行级锁;表级锁 问题 : ① 在多个线程访问共享资源时,会发生线程安全问题,例如:在根据订单号生成订单时,若用户第一次由于某种原因(网络连接不稳定)请求失败,则会再次发生请求,此时便会产生同一

    2024年02月08日
    浏览(35)
  • MySQL乐观锁与悲观锁

    遇见并发情况,需要保证数据的准确性,也就是与正确的预期一致,此时就会用到锁。 锁是在并发下控制程序的执行逻辑,以此来保证数据按照预期变动。 如果不加锁,并发情况下的可能数据不一致的情况,这是个概率问题。 乐观锁很乐观,假设数据一般情况不会造成冲突

    2024年01月23日
    浏览(32)
  • redis实战---乐观锁与悲观锁

    最近一直在研究Redis,今天学习到了乐观锁与悲观锁的部分,在这里进行总结。 Redis是一个内存中的键值存储系统,支持多种数据结构,如字符串、哈希、列表等。 Redis提供了两种锁机制,即乐观锁和悲观锁。 乐观锁是一种乐观的并发控制策略,它认为数据在大多数情况下

    2023年04月09日
    浏览(36)
  • mybatis使用乐观锁和悲观锁

    悲观锁和乐观锁的概念: 悲观锁:就是独占锁,不管读写都上锁了。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。 乐观锁:不上锁,读取的时候

    2024年02月10日
    浏览(33)
  • (学习笔记-进程管理)什么是悲观锁、乐观锁?

    最底层的两种就是 [互斥锁和自旋锁],有很多高级的锁都是基于它们实现的。可以认为它们是各种锁的地基,所以我们必须清楚它们之间的区别和应用。 加锁的目的就是保证共享资源在任意时间内,只有一个线程访问,这样就可以避免多线程导致共享数据错乱的问题。 当已

    2024年02月11日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包