热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

java锁策略

文章目录锁的分类一、乐观锁VS悲观锁二、读写锁三、可重入锁VS不可重入锁四、重量级锁VS轻量级锁五、公平锁VS非公平锁六、自旋锁VS挂起等待锁七、锁升级策略1、无锁:

文章目录

  • 锁的分类
  • 一、乐观锁 VS 悲观锁
  • 二、读写锁
  • 三、可重入锁 VS 不可重入锁
  • 四、重量级锁 VS 轻量级锁
  • 五、公平锁 VS 非公平锁
  • 六、自旋锁 VS 挂起等待锁
  • 七、锁升级策略
    • 1、无锁:
    • 2、 偏向锁:
    • 3、 轻量级锁
    • 4、重量级锁
    • 总结
  • 八、锁的粗化
  • 九、锁消除




锁的分类

加锁,是一个开销比较大的过程,我们希望在一些特定的场景下,针对场景做出一些取舍,可以让锁更加高效一些。就有了以下不同的锁。

一、乐观锁 VS 悲观锁

乐观锁: 假设一般情况下都不会产生锁冲突或基本没有冲突,只有在数据访问的时候才会判断有没有锁竞争。没有就直接修改数据,如果有锁冲突,然后再去处理。

悲观锁 : 假设一般情况下都会产生锁冲突,每次去访问数据的时候都会先上锁,然后再去访问数据。

二、读写锁

ReadWriteLock 管理一组锁,一个是读锁(共享锁),一个是写锁(互斥锁),读写锁默认是悲观锁策略
读锁:可以在没有写锁的时候被多个线程同时持有,但写锁是独占的,同时只能有一个线程去写。

一个获得了读锁的线程必须能看到前一个释放的写锁更新的内容。读写锁比互斥锁允许对于数据更大程度上的并发。读写锁适用于读多写少的情况,此时使用读写锁,就能大大的提高效率。

例:
假设有10个线程,t1 和 t2 是写线程,t3 - t9是读线程。

  1. 如果 t3 和t4 两个线程同时访问数据,此时两个读锁之间不会互斥,完全并发的执行。
  2. 如果 t1 和 t3 两个线程同时访问,此时读锁和写锁之间就会互斥,要么是读完再写,要么是写完再读。
  3. 如果是 t1 和 t2 两个写线程溶蚀访问,此时写锁和写锁之间就会互斥,执行过程一定是一个线程写完,另一个线程再写。

三、可重入锁 VS 不可重入锁

一个线程针对同一把锁连续加锁两次,如果出现死锁,就是 不可重入锁,如果没有出现死锁,就是可重入锁。

对于可重入锁,当前的锁会记录这个锁是谁持有的,如果发现,当前有同一个线程再次尝试获取锁。这个时候,就让代码能够继续运行,而不是阻塞等待。同时这个锁里面也维护一个计数器,这个计数器记录了当前这个线程,针对这把锁加了几次锁,每次加锁,计数器 +1 ,每次解锁,计数器 -1 ,直到计数器为零,此时才会真正的释放锁,其他线程才能才能够获取到这个锁。

四、重量级锁 VS 轻量级锁

加锁很重要的特性就是要保证原子性,原子性的共功能其实来源于硬件(硬件提供了相应的原子操作指令),所以在加锁的过程中,如果整个加锁的逻辑过程都是依赖于操作系统内核,那此时就是重量级锁,此时代码在内核中的开销很大。如果大多数操作,都是由用户自己完成的,少数由操作系统完成就是轻量级锁。

重量级锁 : 加锁、解锁的开销很大,往往是通过内核来完成的
轻量级锁: 加锁、解锁的开销很小,往往只是在用户态完成的。

不同的锁策略之间,并不是完全互不相关的,可能会有部分的重叠:
如 :
悲观锁,做的工作往往更过,因此开销也就更大,悲观锁大概率是重量级锁
乐观锁,做的工作往往更少 , 因此开销也更小,乐观锁大概率是轻量级锁

五、公平锁 VS 非公平锁

公平锁: 如果遵循先来后到的原则,多个线程按照申请锁的顺序去获得锁,线程会直接进入到队列去排队,永远是队列的第一位才能获取到锁。

优点: 所有的线程都能够得到资源,不会饿死再队列中
缺点 : 吞吐量会下降很多,队列里面处理第一个线程,其他的线程都会阻塞,cpu 唤醒阻塞线程的开销比较大。

非公平锁: 不遵循先来后到的原则,多个线程抢占式执行,获取不到锁的再去进入等待队列

优点:可以减少 CPU 唤醒线程的开销,整体的吞吐效率比较高
缺点 :由于所有的线程都在抢占执行,可能会导致队列中的线程一直获取不到锁导致饿死的现象。


六、自旋锁 VS 挂起等待锁

自旋锁: 如果线程获取不到锁,不是阻塞等待而是循环的快速的再是一次~~,因此就节省了操作系统调度线程的开销,要比挂起等待锁更能及时的获取到锁。(假设有两个线程,线程1 拿到了锁,线程2就会不断的通过循环来尝试获取这个锁,一旦线程1 释放了锁,线程2 就能够第一时间时间获取到这个锁,与此而来的问题是,自旋锁更浪费 CPU 资源,通常都是轻量级的锁)

挂起等待锁: 如果线程获取不到锁,就会阻塞等待,具体什么时候结束阻塞,取决于操作系统的具体调度,当线程挂起的时候,不占用 CPU 资源,通常都是重量级锁


应用场景:

  1. 如果锁的冲突的概率比较低,使用自旋锁比挂起等待锁,更合适
  2. 如果线程持有锁的时间比较短,使用自旋锁比挂起等待锁更合适
  3. 如果对 CPU 比较敏感,不希望占用太多的 CPU 资源,那么就不太适合使用自旋锁

七、锁升级策略

java对象在内存中的布局,可分为三个部分:
在这里插入图片描述

  1. 对象头:对象头主要包括 Mark Word(标记字段),用于存储对象自身的运行时数据。Klass Pointer(类型指针),对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
  2. 实例变量 : 存储的是对象的属性信息,包括父类的属性信息,按照4字节对齐
  3. 填充字节 : 因为虚拟机要求对象字节必须是8字节的整数倍,填充字节只是为了内存对齐
    在这里插入图片描述
    Java SE1.6 为了减少获得锁和释放锁所带来的性能消耗,引入了“偏向锁”和“轻量级锁”,所以在 Java SE1.6 里锁一共有四种状态,无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,它会随着竞争情况逐渐升级。

锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。

在这里插入图片描述

1、无锁:

没有对资源进行锁定,多有的线程都能够访问并修改同一个资源,但同时只有一个线程能修改成功
无锁的特点是修改操作会在循环内进行,线程会不断的尝试修改共享资源。如果没有冲突就修改成功并退出,如果有冲突就会循环尝试。如果有多个线程修改同一个值,必定会有一个线程能修改成功,而其他修改失败的线程会不断重试直到修改成功。

2、 偏向锁:

偏向锁引入:
经过研究,大多数时候是不存在锁竞争的,常常是一个线程多次获得同一个锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁。

偏向锁升级过程:

偏向锁是指当一段同步代码一直被同一个线程访问时,即不存在多个线程的竞争时,那么该线程在后续访问时变会自动获得锁,从而降低获取锁代理的消耗。

当线程1 在访问同步代码块并获取到锁时,会在java对象头和栈帧中记录偏向的锁的线程ID,因为偏向锁不会主动释放锁,(偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的)

因此以后线程1在次获取锁的时候,只需要比较当前线程的ID 和 java对象头中的线程ID是否一致:
如果一致,则无需使用CAS来加锁,解锁;
如果不一致,(如线程2需要竞争锁对象,而偏向锁不会主动释放因此还是存储1的ID ),那么就需要查看java对象头中记录的线程1是否存货,如果没有存货,那么锁对象被重置位无所状态,其他线程可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程(线程1)的的栈帧信息,如果还是需要继续下hi有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级位轻量级锁,如果线程1不再使用该锁对象,那么将锁对象状态设置为无锁状态,重新偏向新的线程

3、 轻量级锁

轻量级锁引用:
轻量级锁考虑的是竞争多对象的线程不多,而且线程持有锁的时间也不长的情景。因为阻塞线程需要 CPU 从用户态转到内核态,代价比较大,如果刚刚阻塞不久,这个锁就被释放了,这个开销就很大,因此这个时候就干脆不阻塞这个线程,让他自旋着等待锁释放。线程不会阻塞,从而提高了性能

轻量级锁的获取主要有两种情况

  • 当关闭偏向锁功能时
  • 由于多个线程竞争偏向锁导致偏向锁升级为轻量级锁。

线程1获取轻量级锁时,会先把锁对象头 MarkWord 复制一份到线程1的栈帧中创建的用于存储锁记录的空间(称为DisplaceMarkWord),然后使用 CAS 把对象头中的内容替换位线程1存储的锁记录(DisplaceMarkWord)的地址;

如果在线程1复制对象的同时(在线程1 CAS之前),线程2也准备获取锁,复制了对象头到线程2的锁记录空间中,但是在2线程 CAS的时候,发现线程1已经把对象头换了,线程2CAS 失败,那么线程2就尝试使用自旋锁来等待线程1释放锁。(线程2不断CAS)

但是如果自旋时间太长,因为自旋是要消耗CPU的,因此自旋的次数是由限制的,如果自旋次数到了,线程1还没有释放锁,或者线程1 还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个轻量级锁就会膨胀为重量级锁。

4、重量级锁

重量级锁,是指当一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态,除拥有锁的线程都阻塞。

重量级锁是通过对象内部的监视器实现,而其中monitor的本质是依赖底层操作系统 Mutex Lock 实现,操作系统实现线程之间的切换需要从用户态切换到内核态,切换成本非常高。

由操作系统来负责线程间的调度和线程的状态变更。而这样出现偏饭的对线程云从状态的切换,线程的挂起和唤醒,从而消耗大量的系统资源,导致性能降低。

总结

在这里插入图片描述

八、锁的粗化

通常情况下,为了保证多线程间的有效并发,会尽量将同步代码块作用的范围控制的尽量小,只在共享的数据的实际作用域中才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,拿等待的锁的线程也能尽快拿到锁。

但是如果一个程序对同一个锁不间断、高频繁的反复加锁与解锁,会消耗不必要的资源开销,这样高频的锁请求反而不利于系统性能的优化。如果虚拟机探测到有这样遗传零碎的操作都对同一个对象加锁,将会把锁的范围扩展到整个操作程序的外部,合并成一个请求,以降低短时间内大量锁请求,同步。释放带来的性能损耗

fun(){synchronized(this){//任务1}synchronized(this){//任务2}synchronized(this){//任务3}
}

上面的代码会不断的加锁,释放锁,造成不必要的系统开销,与其这样还不如一次加锁完成三个任务

fun(){synchronized(this){//任务1//任务2//任务3}
}

九、锁消除

锁消除,是指虚拟机在执行编译器运行时,会一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。显然这里有锁,但是可以被安全的消除掉,在即时编译之后。这段代码就会忽略掉所有的同步而直接执行。


推荐阅读
  • 本文档详细介绍了服务器与应用系统迁移的策略与实施步骤。迁移不仅涉及数据的转移,还包括环境配置、应用兼容性测试等多个方面,旨在确保迁移过程的顺利进行及迁移后的系统稳定运行。 ... [详细]
  • 本文详细探讨了在Windows Server 2003环境下遇到MySQL连接失败(错误代码10061)的解决方案,包括通过卸载特定的Windows更新和调整系统注册表设置的方法。 ... [详细]
  • mysql 分库分表策略_【数据库】分库分表策略
    关系型数据库本身比较容易成为系统瓶颈,单机存储容量、连接数、处理能力都有限。当单表的数据量达到1000W或100G以后,由于查询维度较多, ... [详细]
  • 在现代多线程编程中,Lock接口提供的灵活性和控制力超越了传统的synchronized关键字。Lock接口不仅使锁成为一个独立的对象,还提供了更细粒度的锁定机制,例如读写锁(ReadWriteLock)。本文将探讨如何利用ReentrantReadWriteLock提高并发性能。 ... [详细]
  • 本文详细探讨了 Java 中 Daemon 线程的特点及其应用场景,并深入分析了 Random 类的源代码,帮助开发者更好地理解和使用这些核心组件。 ... [详细]
  • 本文探讨了Go语言(Golang)的学习价值及其在Web开发领域的应用潜力,包括其独特的语言特性和为什么它是现代软件开发的理想选择。 ... [详细]
  • 本文介绍了Android中常见的动画类型及其应用场景,通过具体的代码示例展示了如何在Activity跳转时添加平滑过渡效果,提升用户体验。 ... [详细]
  • CyclicBarrier是一种同步辅助类,能够在多个线程到达某个屏障点时进行阻塞,直到所有参与的线程都达到这一屏障点后,所有线程才继续执行。本文将详细介绍CyclicBarrier的工作原理及应用场景。 ... [详细]
  • 快速排序是一种高效的排序算法,以其在多数情况下接近最优的性能而著称。本文将详细介绍如何在 Java 中实现快速排序,并分析其工作原理。 ... [详细]
  • 本文探讨了命令模式和责任链模式在软件设计中的应用,详细介绍了这两种模式的基本概念、UML图示、主要组成部分及其优缺点。特别关注了命令模式如何通过命令转发实现调用者与接收者之间的解耦,以及责任链模式如何通过顺序传递请求来调整处理逻辑。 ... [详细]
  • 本文详细介绍了RocketMQ中的消息并发消费机制,包括消息拉取后的处理流程、消费服务的调用以及消费任务的具体执行过程。 ... [详细]
  • 本文详细探讨了UML用例图中的两种重要关系——包含关系和扩展关系,通过具体示例解析这两种关系的应用场景及其实现方式。 ... [详细]
  • 本文探讨了一起由物化视图统计信息不当引起的查询性能下降问题,并详细介绍了问题的诊断与解决方法。通过调整统计信息收集策略,最终显著提升了查询效率。 ... [详细]
  • RabbitMQ消息分发策略与确认机制
    本文详细介绍了RabbitMQ的消息分发轮询机制以及消息确认(Message Acknowledgment)功能,通过实例演示了如何确保消息可靠传递。 ... [详细]
  • 本文详细介绍了MySQL 5.5及以上版本中事务管理的全过程,包括事务的启动、设置、锁机制以及解锁方法,旨在为开发者提供一个清晰、全面的操作指南,避免因网络资料分散而导致的学习障碍。 ... [详细]
author-avatar
吉翠芙_899
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有