【六大锁策略-各种锁的对比-Java中的Synchronized锁和ReentrantLock锁的特点分析-以及加锁的合适时机】

这篇具有很好参考价值的文章主要介绍了【六大锁策略-各种锁的对比-Java中的Synchronized锁和ReentrantLock锁的特点分析-以及加锁的合适时机】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

系列文章目录



前言

阅读该文章之前要了解,锁策略是为了解决什么问题

多线程带来的的风险-线程安全的问题的简单实例-线程不安全的原因


提示:以下是本篇文章正文内容,下面案例可供参考

一、六大"有锁策略"

锁冲突是指两个线程对一个对象加锁,产生了阻塞等待。

1. 乐观锁——悲观锁

乐观锁

  • 假设数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。

  • 预测接下来的锁冲突不大(一般消耗的资源少,效率高点)

悲观锁

  • 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。

  • 预测接下来的锁冲突很大(一般消耗的资源多,效率低点)


举个例子

大学里的期末的最后一门考试结束后,当天,辅导员就会通知假期就开始了,大家可以离校了。

  • 同学A乐观锁)认为:反正,每次都是考试完,就可以直接走了,于是他就直接收拾行李,不等通知,直接当时就回家了,出现意外再说。

  • 同学B悲观锁)认为:万一辅导员说这次放假延迟,大家都留校等领导通知,于是他就在宿舍一直等到辅导员通知,才开始收拾行李,出发回家。

此时,在B等待的时间里,A可能已经到家了。(即A的回家效率高于B


2. 轻量级锁——重量级锁

该文中出现的“乐观锁”“偏向锁”“等都会在后面介绍,读者不必先理解,可先大致有个印象

轻量级锁

  • 加锁解锁,过程更快更高效

  • 轻量级锁在Java中是一种乐观锁的方式,使用CAS(比较和交换)实现,它是通过在对象头中标记为“偏向锁”来实现的。当一个线程获得该偏向锁时,它就可以直接访问被锁定的对象,而不用执行任何额外的同步操作。如果有其他线程来访问该对象,轻量级锁就会自动退化为重量级锁。


重量级锁

  • 加锁解锁,过程更慢更低效

  • 重量级锁在Java中是一种悲观锁的方式,使用互斥锁(Mutex Lock)实现,它需要操作系统的支持。当有多个线程同时访问一个共享资源时,重量级锁会把其他线程阻塞,直到当前线程执行完毕,释放锁。这种方式的效率较低,因为线程的上下文切换和系统调用开销较大。

总结

  1. 轻量级锁适用于竞争不激烈的情况,而重量级锁适用于竞争激烈的情况。在实际开发中,我们需要根据具体场景选择合适的锁机制,以达到最佳的性能。

  2. 同时,乐观锁可能是轻量级锁,悲观锁可能是重量级锁(不绝对)


3. 自旋锁——挂起等待锁

自旋锁

  • 一直占用CPU,不涉及线程阻塞和调度,持续不断的请求锁,一但锁被释放,就能立即得到,忙等
  • 如果其他线程一直不释放锁,那它就一直持续消耗CPU资源(该代码通常是纯用户态,不会设置很长的时间)

挂起等待锁

  • 当它发现没有锁的时候,就会进入挂起等待状态(挂机),挂起等待的时候是
    不消耗 CPU的

  • 它等待操作系统的通知唤醒,但是可能其他线程刚释放了锁,就被一直不断请求的自旋锁线程给枪走了,所以它只能继续等待,具体拿到锁的时机,还得听从操作系统的安排(该锁一般是内核机制,可能会等待较长的时间)

对照前文

  1. 自旋锁是轻量级锁的一种典型实现

  2. 挂起等待锁是重量级锁的一种典型实现


4. 互斥锁——读写锁

互斥锁(例如:synchronized)

只有两个操作:

  1. 进入代码块,给该代码块加锁。

  2. 出代码块,解锁该带代码块。

  3. 互斥锁常用于保护共享数据结构的访问,如队列、链表、散列表等。需要注意的是,互斥锁使用不当可能会带来锁竞争、死锁等问题,

读写锁(例如:ReentrantReadWriteLock)

  1. 给读操作加锁。(读锁,是一种共享锁,可被多个线程同时拥有。当读锁被占用时,其他读锁可以继续被占用。共享性。)

  2. 给写操作加锁。(写锁,写锁是一种排他锁,只能被一个线程占用,当写锁被占用时,其他任何锁都不能被占用。原子性。)

  3. 解锁。

  4. 多个线程同时读取一个变量,不会涉及到线程安全问题。读写锁适用于对共享资源的读操作频繁,写操作较少的情况,如高并发读,比如缓存、数据维护等。读写锁可以提高读取效率,避免了互斥锁的性能开销。同时,写操作的排他特性避免了并发写操作对共享资源的影响,保证数据的正确性和一致性。

在读锁和写锁之间,约定:

  • 读锁和读锁之间,不会锁竞争,不会产生阻塞等待。(不会影响执行速度)

  • 写锁和写锁之间,有锁竞争。(不会影响执行速度)

  • 读锁和写锁之间,有锁竞争。(会影响速度,但是保证线程安全)


5. 可重入锁——不可重入锁

可重入锁,又名递归锁(例如:synchronized)

  1. 如果一个锁,在一个线程中,连续加锁两次,不死锁,就叫做可重入锁,死锁了,就叫不可重入锁。即允许同一个线程多次获取同一把锁,而不会产生死锁。

  2. 这种锁能够保证同一线程多次访问同一资源时不会发生冲突。

  3. Java里只要以Reentrant开头命名的锁都是可重入锁,而且JDK提供的所有现成的Lock实现类,包括synchronized关键字锁都是可重入的。

代码示例

Object locker = new Object();
synchronized(locker) {
    synchronized(locker) {
        //连续加锁两次    
    }
}

//或者
//这也是两次加锁,针对this
class BlockingQueue {
    synchronized void put(int elem) {
        this.size();
    }
    
    synchronized int size() {}
}

不可重入锁

  1. 同一线程第二次加锁的时候, 会阻塞等待。直到第一次的锁被释放, 才能获取到第二个锁。 但是释放第一个锁也是由该线程来完成, 结果这个线程已经阻塞了, 也就无法进行解锁操作.。这时候就会死锁。

  2. 即在同一线程再次请求获得该锁时,会造成死锁。因为该锁只能被获得一次,并且只有获得锁的线程才能释放锁。

  3. Linux系统提供的 mutex是不可重入锁.


6. 公平锁——非公平锁

公平锁

  1. 是指多个线程按照申请锁的顺序来获取锁,即先到先得的策略。(公不公平是由自己对公平的定义决定,Java中定义先到先得为公平,synchronized为非公平锁,它遵循等概率竞争规则)

  2. 公平锁的优点是可以避免饥饿现象,即线程在获取锁时会受到先来先服务的原则,公平性是保证锁最大程度分配给等待时间最长的线程,缺点是其效率较低,因为需要保存大量的线程状态。

非公平锁

  1. 多个线程获取锁的顺序是不确定的,有可能后申请锁的线程先获取到锁,这种方式可能造成某些线程一直无法获取到锁。

  2. 在Java中,ReentrantLock默认就是非公平锁。与公平锁相比,非公平锁调度的效率要高,但是不公平的分配策略可能会导致某些线程一直无法获取到锁,从而产生“饥饿”的现象。

  3. 在Java中,ReentrantLock默认是非公平锁,可以通过它的构造函数改为公平锁。


二、Synchronized——ReentrantLock

Synchronized的特点(JDK1.8)

  1. 开始时是乐观锁,如果锁冲突频繁,就转换为悲观锁。

  2. 开始时轻量级锁,如果锁被持有时间较长,就转换为重量级锁。

  3. 轻量级锁大概率基于自旋实现,重量级锁大概率基于挂起等待实现。

  4. 不是读写锁。

  5. 是可重入锁。

  6. 是非公平锁。

Synchronized的锁升级策略

都是尽可能减少锁带来的的开销

【六大锁策略-各种锁的对比-Java中的Synchronized锁和ReentrantLock锁的特点分析-以及加锁的合适时机】,java,开发语言,java-ee,后端

  • 无锁

  • 偏向锁(非必要不加锁

即线程对锁有个标记,没有竞争就不加锁,倘若有别的线程竞争,就立即加锁,即高效又安全

  • 自旋锁 / 轻量级锁(遇到了锁竞争,但是目前线程较少,就让它自旋一会,说不定很快就拿到了 )

  • 重量级锁(线程竞争激烈,多个线程都在自旋,大量占用cpu资源,直接升级锁,调用系统内核阻塞等待)

主流的JVM只能锁升级,不能降级,不是实现不了,可能需要付出更大的代价,于是干脆就不降级了

ReentrantLock的特点

  1. 可重入:同一个线程可以多次获取锁,避免了死锁的发生。

  2. 公平锁和非公平锁:ReentrantLock可以通过参数指定是公平锁还是非公平锁。

  3. 条件变量:ReentrantLock可以通过维护条件变量来实现线程间的协调。

  4. 中断响应:ReentrantLock支持线程中断,即在等待锁的过程中,可以响应中断信号。

  5. 限时等待:ReentrantLock支持线程等待一定时间,如果在指定时间内还未获取到锁,就会放弃等待。

Synchronized和ReentranLock对比

  1. ReentranLock是可重入锁,提供lock()unlock()独立方法(即需要手动释放),来进行加锁解锁,synchronized也是可重入锁(基于代码块的方式来控制加锁解锁),它在第二次加锁之前,会判定当前锁的拥有者是否是同一个线程,如果是,则直接放行,不必再加一次锁

  2. synchronized是非公平的,若想要公平,需要手动加个优先级队列来记录顺序。ReentrantLock提供公平和非公平两种工作模式,默认是非公平锁, 在构造方法中传入true,开启公平锁。

  3. synchronized搭配Objectwaitnotify进行等待唤醒,如果多个线程wait()同一个对象,notify()随机唤醒一个。ReentrantLock需要搭配Condition这个类,这个类也能起到等待通知的作用,能够精准唤醒某个线程, 功能更强大。

  4. synchronized是一个关键字, 是 JVM内部实现的(大概率是基于 C++ 实现). ReentrantLock是标准库的一个类, 在 JVM 外实现的(基于Java实现)

  5. synchronized在申请锁失败时, 会死等. ReentrantLock可以通过 trylock()的方式等待一段时间就放弃, 不会阻塞,而是返回false(让用户自己决定后续操作)。

三、锁消除——锁粗化

锁消除

  • 非必要不加锁(不滥用synchronized)

  • 编译器+JVM就会会作出优化,检测当前代码是否是多线程执行 / 是否有必要加锁,如果没必要,就自动把锁去掉。

例如:StringBuilder和StringBuffer,后者带锁,但是如果单线程使用后者,就自动将后者优化为前者。(该手段十分保守,只有保证消除是可靠的,才会启动,宁愿什么也不做,也不愿意犯错

锁粗化

【六大锁策略-各种锁的对比-Java中的Synchronized锁和ReentrantLock锁的特点分析-以及加锁的合适时机】,java,开发语言,java-ee,后端

  • 锁的粒度,synchronized代码块,包含代码的多少(代码越多,粒度越粗。代码越少,粒度越细)

  • 一般写代码,多数情况下,希望粒度小一些。(串行执行的代码少,并发执行的代码多)但是如果某个场景,频繁的加锁/解锁,此时编译器就会把它优化为一个更粗粒度的锁。文章来源地址https://www.toymoban.com/news/detail-615012.html

到了这里,关于【六大锁策略-各种锁的对比-Java中的Synchronized锁和ReentrantLock锁的特点分析-以及加锁的合适时机】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • synchronized各种使用场景

    开启10个线程,每个线程中循环100次对result变量进行++自增,主线程等待10s后输出result值结果 结果 执行结果不一定是869,可能是其他的数,总之就是比正确的结果1000小 原因 result++这个操作的执行过程其实是3个步骤 读取result变量 将result变量进行+1 将result值再赋给result变量 详

    2023年04月23日
    浏览(43)
  • JUC并发编程(终章)各种锁的理解

    公平锁、非公平锁 公平锁:先到先得(不可插队) 非公平锁:达者为先(可插队)----------默认 可重入锁(递归锁) 所有的锁都是可重入锁 Synchronized版 ems方法中包含了call方法,所以当我们调用ems方法获取到锁时,也把call方法的synchronized锁获取到了。 错误理论 当线程A运行

    2024年02月05日
    浏览(72)
  • 【JavaEE】锁的策略

    作者主页: paper jie_博客 本文作者:大家好,我是paper jie,感谢你阅读本文,欢迎一建三连哦。 本文于《JavaEE》专栏,本专栏是针对于大学生,编程小白精心打造的。笔者用重金(时间和精力)打造,将基础知识一网打尽,希望可以帮到读者们哦。 其他专栏:《MySQL》《C语言》

    2024年02月04日
    浏览(29)
  • Nginx配置大全【六大使用场景、七大负载均衡策略、四大负载健康检查】

    !!! 反向代理也可以基于请求路径转发到不同服务器 !!! !!!反向代理路径结尾加不加 / 符号的区别!!! 如果只是简单的重定向操作,并且不需要进行复杂的路径重写或捕获,推荐使用 return 301 的方式来实现重定向。这样能够更直接、更高效地达到重定向的目的,避免不必要的

    2024年04月16日
    浏览(54)
  • Java 中的各种锁

    对于线程是否需要锁住共享的资源,我们可以将其分为乐观锁与悲观锁,前者不会锁住共享资源后者会将共享资源进行锁住。 乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新时会判断此

    2024年02月03日
    浏览(29)
  • 【JavaEE初阶】多线程进阶(五)常见锁策略 CAS synchronized优化原理

    乐观锁:预测锁竞争不是很激烈。 悲观锁:预测锁竞争会很激烈。 以上定义并不是绝对的,具体看预测锁竞争激烈程度的结论。 轻量级锁加锁解锁开销比较小,效率更高。 重量级锁加锁解锁开销比较大,效率更低。 多数情况下,乐观锁也是一个轻量级锁。 多数情况下,悲

    2024年02月03日
    浏览(46)
  • JAVA中的各种循环语句

    目录 一、if循环 二、if与else if循环的运用 三、while循环 四、for循环 我下面都用案例来解释和展示循环,大家结合案例和注释多加感悟,将会对Java循环有个不错了解。   下面为一个输入成绩判定情况 if与else if同时使用可以筛选多个条件。最后一个else可以筛选以上不满足的情

    2023年04月08日
    浏览(34)
  • Redis实战案例14-分布式锁的基本原理、不同实现方法对比以及基于Redis进行实现思路

    基于数据库的分布式锁:这种方式使用数据库的特性来实现分布式锁。具体流程如下: 获取锁:当一个节点需要获得锁时,它尝试在数据库中插入一个特定的唯一键值(如唯一约束的主键),如果插入成功,则表示获得了锁。 释放锁:当节点完成任务后,通过删除该唯一键

    2024年02月13日
    浏览(50)
  • Java中的各种引用类型以及部分引用的相关例子

    在Java中,引用类型主要有四种,分别是:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)。这些类型通常与垃圾回收机制有关,用来描述对象的生命周期和可达性。下面详细介绍每一种引用类型: 强引用 (Strong Reference) 强

    2024年04月13日
    浏览(42)
  • 一文全览各种 ES 查询在 Java 中的实现

    以下为摘录自用,非本人撰写 本文基于elasticsearch 7.13.2版本,es从7.0以后,发生了很大的更新。7.3以后,已经不推荐使用TransportClient这个client,取而代之的是Java High Level REST Client。 首先是,Mysql中的部分测试数据: Mysql中的一行数据在ES中以一个文档形式存在: 简单梳理了一

    2024年02月11日
    浏览(38)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包