热门标签 | 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}
}

九、锁消除

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


推荐阅读
  • FinOps 与 Serverless 的结合:破解云成本难题
    本文探讨了如何通过 FinOps 实践优化 Serverless 应用的成本管理,提出了首个 Serverless 函数总成本估计模型,并分享了多种有效的成本优化策略。 ... [详细]
  • 并发编程:深入理解设计原理与优化
    本文探讨了并发编程中的关键设计原则,特别是Java内存模型(JMM)的happens-before规则及其对多线程编程的影响。文章详细介绍了DCL双重检查锁定模式的问题及解决方案,并总结了不同处理器和内存模型之间的关系,旨在为程序员提供更深入的理解和最佳实践。 ... [详细]
  • 作者:守望者1028链接:https:www.nowcoder.comdiscuss55353来源:牛客网面试高频题:校招过程中参考过牛客诸位大佬的面经,但是具体哪一块是参考谁的我 ... [详细]
  • 深入解析JVM垃圾收集器
    本文基于《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版,详细探讨了JVM中不同类型的垃圾收集器及其工作原理。通过介绍各种垃圾收集器的特性和应用场景,帮助读者更好地理解和优化JVM内存管理。 ... [详细]
  • 本文深入探讨了Linux系统中网卡绑定(bonding)的七种工作模式。网卡绑定技术通过将多个物理网卡组合成一个逻辑网卡,实现网络冗余、带宽聚合和负载均衡,在生产环境中广泛应用。文章详细介绍了每种模式的特点、适用场景及配置方法。 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • Linux设备驱动程序:异步时间操作与调度机制
    本文介绍了Linux内核中的几种异步延迟操作方法,包括内核定时器、tasklet机制和工作队列。这些机制允许在未来的某个时间点执行任务,而无需阻塞当前线程,从而提高系统的响应性和效率。 ... [详细]
  • 基于KVM的SRIOV直通配置及性能测试
    SRIOV介绍、VF直通配置,以及包转发率性能测试小慢哥的原创文章,欢迎转载目录?1.SRIOV介绍?2.环境说明?3.开启SRIOV?4.生成VF?5.VF ... [详细]
  • 深入探讨CPU虚拟化与KVM内存管理
    本文详细介绍了现代服务器架构中的CPU虚拟化技术,包括SMP、NUMA和MPP三种多处理器结构,并深入探讨了KVM的内存虚拟化机制。通过对比不同架构的特点和应用场景,帮助读者理解如何选择最适合的架构以优化性能。 ... [详细]
  • 本文探讨了 Spring Boot 应用程序在不同配置下支持的最大并发连接数,重点分析了内置服务器(如 Tomcat、Jetty 和 Undertow)的默认设置及其对性能的影响。 ... [详细]
  • 本文探讨了在Java多线程环境下,如何确保具有相同key值的线程能够互斥执行并按顺序输出结果。通过优化代码结构和使用线程安全的数据结构,我们解决了线程同步问题,并实现了预期的并发行为。 ... [详细]
  • 深入解析TCP/IP五层协议
    本文详细介绍了TCP/IP五层协议模型,包括物理层、数据链路层、网络层、传输层和应用层。每层的功能及其相互关系将被逐一解释,帮助读者理解互联网通信的原理。此外,还特别讨论了UDP和TCP协议的特点以及三次握手、四次挥手的过程。 ... [详细]
  • 在Java中,this是一个引用当前对象的关键字。如何通过this获取并显示其所指向的对象的属性和方法?本文详细解释了this的用法及其背后的原理。 ... [详细]
  • 2018年3月31日,CSDN、火星财经联合中关村区块链产业联盟等机构举办的2018区块链技术及应用峰会(BTA)核心分会场圆满举行。多位业内顶尖专家深入探讨了区块链的核心技术原理及其在实际业务中的应用。 ... [详细]
  • 本文详细介绍了C语言中的指针,包括其基本概念、应用场景以及使用时的优缺点。同时,通过实例解析了指针在内存管理、数组操作、函数调用等方面的具体应用,并探讨了指针的安全性问题。 ... [详细]
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社区 版权所有