首页 > 编程语言 >java锁总结

java锁总结

时间:2023-08-04 12:37:28浏览次数:52  
标签:总结 加锁 java 获取 线程 自旋 操作 轻量级

Java 中的锁主要用于保障多并发线程情况下数据的一致性。在多线程编程中为了保障数据的一致性,我们通常需要在使用对象或者方法之前加锁,这时如果有其他线程也需要使用该对象或者该方法,则首先要获得锁,如果某个线程发现锁正在被其他线程使用,就会进入阻塞队列等待锁的释放,直到其他线程执行完成并释放锁,该线程才有机会再次获取锁进行操作。这样就保障了在同一时刻只有一个线程持有该对象的锁并修改对象,从而保障数据的安全。

锁分类

乐观锁

乐观锁采用乐观的思想处理数据,在每次读取数据时都认为别人不会修改该数据所以不会上锁,但在更新时会判断在此期间别人有没有更新该数据,通常采用在写时先读出当前版本号然后加锁的方法。具体过程为:比较当前版本号与上一次的版本号,如果版本号一致,则更新,如果版本号不一致,则重复进行读、比较、写操作。

Java中的乐观锁大部分是通过 CAS(Compare And Swap,比较和交换)操作实现的CAS 是一种原子更新操作,在对数据操作之前首先会比较当前值跟传入的值是否一样,如果一样则更新,否则不执行更新操作,直接返回失败状态。

悲观锁

悲观锁采用悲观思想处理数据,在每次读取数据时都认为别人会修改数据,所以每次在读写数据时都会上锁,这样别人想读写这个数据时就会阻塞、等待直到拿到锁。

Java 中的悲观锁大部分基于 AQS (Abstract Queued Synchronized,抽象的队列同步器)架构实现。AOS 定义了一套多线程访问共享资源的同步框架,许多同步类的实现都依赖于它,例如常用的 Synchronized、ReentrantLock、Semaphore、CountDownLatch 等。该框架下的锁会先尝试以 CAS 乐观锁去获取锁,如果获取不到,则会转为悲观锁 (如RetreenLock)。

公平锁与非公平锁

从获取资源的公平性角度可分为公平锁和非公平锁。

  1. 公平锁是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。在并发环境中,每个线程在获取到锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己。
  2. 非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比现申请的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象。在并发环境中,每个线程上来就直接尝试占有锁,如果尝试失败,就会采用类似公平锁那种方式。

因为公平锁需要在多核的情况下维护一个锁线程等待队列,基于该队列进行锁的分配,因此效率比非公平锁低很多。Java 中的 synchronized 是非公平锁,ReentrantLock 默认的 lock 方法采用的是非公平锁,也可通过构造函数指定该锁是否是公平锁

共享锁与独占锁

从是否共享资源的角度可分为共享锁和独占锁。

  1. 独占锁:也叫互斥锁,每次只允许一个线程持有该锁,ReentrantLock 为独占锁的实现。
  2. 共享锁:允许多个线程同时获取该锁,并发访问共享资源。 ReadWriteLock 中的读锁为共享锁的实现。

独占锁是一种悲观的加锁策略,同一时刻只允许一个读线程读取锁资源,限制了读操作的并发性:因为并发读线程并不会影响数据的一致性,因此共享锁采用了乐观的加锁策略,允许多个执行读操作的线程同时访问共享资源。

AQS 的内部类 Node 定义了两个常量 SHARED 和 EXCLUSIVE,他们分别标识AQS 队列中等待线程的锁获取模式。

java 的并发包中提供了 ReadWriteLock,读写锁。它允许一个资源可以被多个读操作访问或者被一个写操作访问,但两者不能同时进行。

自旋锁

自旋锁:如果持有锁的线程能在很短的时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞、挂起状态,只需等一等(也叫作自旋 ),在等待持有锁的线程释放锁后即可立即获取锁,这样就避免了用户线程在内核状态的切换上导致的锁时间消耗。

线程在自旋时会占用 CPU,在线程长时间自旋获取不到锁时,将会产生CPU的浪费甚至有时线程永远无法获取锁而导致 CPU 资源被永久占用,所以需要设定一个自旋等待的最大时间。在线程执行的时间超过自旋等待的最大时间后,线程会退出自旋模式并释放其持有的锁。

  • 优点:自旋锁可以减少 CPU 上下文的切换,对于占用锁的时间非常短或锁竞争不激烈的代码块来说性能大幅度提升,因为自旋的 CPU 耗时明显少于线程阻塞挂起、再唤醒时两次 CPU 上下文切换所用的时间。
  • 缺点:在持有锁的线程占用锁时间过长或锁的竞争过于激烈时,线程在自旋过程中会长时间获取不到锁资源,将引起 CPU 的浪费。所以在系统中有复杂锁依赖的情况下不适合采用自旋锁

JDK 的不同版本所采用的自旋周期不同,JDK 1.5 为固定的时间,JDK 1.6引入了适应性自旋锁。适应性自旋锁的自旋时间不再是固定值,而是由上一次在同一个锁上的自旋时间及锁的拥有者的状态来决定的,可基本认为一个线程上下文切换的时间就是一个最佳时间。

可重入锁

可重入锁也叫作递归锁,指在同一线程中,在外层函数获取到该锁之后,内层的递归函数仍然可以继续获取该锁。在 Java 环境下,ReentrantLock 和 synchronized 都是可重入锁。

synchronized

synchronized 关键字用于为 Java 对象、方法、代码块提供线程安全的操作。synchronized 属于独占式的悲观锁,同时属于可重入锁。在 synchronized 修饰对象、方法、代码块时,同一时刻只能有一个线程访问对象、执行该方法体或代码块,其他线程只有等待当前线程执行完毕并释放锁资源后才能访问该对象或执行同步代码块。

Java 中的每个对象都有个 monitor 对象,加锁就是在竞争 monitor 对象。对代码块加锁是通过在前后分别加上 monitorenter 和 monitorexit 指令实现的,对方法是否加锁是通过一个标记位来判断的。

synchronized 作用于成员变量和非静态方法时,锁住的是对象的实例,即 this 对象。

synchronized 作用于静态方法时,锁住的是 Class 实例,因为静态方法属于 Class而不属于对象。

synchronized 作用于一个代码块时,锁住的是所有代码块中配置的对象

ReentrantLock

ReentrantLock 继承了 Lock 接口并实现了在接口中定义的方法,是一个可重入的独占锁。ReentrantLock 通过自定义队列同步器 (Abstract Queued Sychronized,AQS)来实现锁的获取与释放。使用lock方法加锁,unlock方法释放锁

  • 独占锁指该锁在同一时刻只能被一个线程获取,而获取锁的其他线程只能在同步队列中等待;可重入锁指该锁能够支持一个线程对同一个资源执行多次加锁操作。
  • ReentrantLock 支持公平锁和非公平锁的实现。
  • ReentrantLock 不但提供了 synchronized 对锁的操作功能,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法。

锁状态

锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁

锁升级

随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的也就是说只能从低到高升级,不会出现锁的降级)

重量级锁(Mutex Lock)

Synchronized 是通过对象内部的一个叫做监视器锁 (monitor) 来实现的。但是监视器锁本质又是依赖于底层的操作系统的 Mutex Lock 来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized 效率低的原因。因此,这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为“重量级锁”。JDK 中对 Synchronized 做的种种优化,其核心都是为了减少这种重量级锁的使用JDK1.6 以后,为了减少获得锁和释放锁所带来的性能消耗,提高性能,引入了“轻量级锁”和"偏向锁”。

轻量级锁

“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的。但是,首先需要强调一点的是轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。

偏向锁

Hotspot 的作者经过以往的研究发现大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得。偏向锁的目的是在某个线程获得锁之后,消除这个线程锁重入 (CAS) 的开销,看起来让这个线程得到了偏护。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次 CAS 原子指令,而偏向锁只需要在置换ThreadID 的时候依赖一次 CAS 原子指令 (由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的 CAS 原子指令的性能消耗)。上面说过,轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进步提高性能

锁优化

分段锁

分段锁并非一种实际的锁,而是一种思想,用于将数据分段并在每个分段上都单独加锁,把锁进一步细粒度化,以提高并发效率。ConcurrentHashMap 在内部就是使用分段锁实现的。

同步锁与死锁

在有多个线程同时被阻塞时,它们之间若相互等待对方释放锁资源,就会出现死锁为了避免出现死锁,可以为锁操作添加超时时间,在线程持有锁超时后自动释放该锁。

锁优化方法

减少锁持有的时间

减少锁持有的时间指只在有线程安全要求的程序上加锁来尽量减少同步代码块对锁的持有时间。

减小锁粒度

减小锁粒度指将单个耗时较多的锁操作拆分为多个耗时较少的锁操作来增加锁的并行度,减少同一个锁上的竞争。在减少锁的竞争后,偏向锁、轻量级锁的使用率才会提高减小锁粒度最典型的案例就是 ConcurrentHashMap 中的分段锁。

锁分离

锁分离指根据不同的应用场景将锁的功能进行分离,以应对不同的变化,最常见的锁分离思想就是读写锁 (ReadWriteLock ),它根据锁的功能将锁分离成读锁和写锁,这样读读不互斥,读写互斥,写写互斥,既保证了线程的安全性,又提高了性能。

操作分离思想可以进一步延伸为只要操作互不影响,就可以进一步拆分,比如LinkedBlockingQueue 从头部取出数据,并从尾部加入数据。

锁粗化

锁粗化指为了保障性能,会要求尽可能将锁的操作细化以减少线程持有锁的时间.但是如果锁分得太细,将会导致系统频繁获取锁和释放锁,反而影响性能的提升。在这种情况下,建议将关联性强的锁操作集中起来处理,以提高系统整体的效率。

锁消除

在开发中经常会出现在不需要使用锁的情况下误用了锁操作而引起性能下降,这多数是因为程序编码不规范引起的。这时,我们需要检查并消除这些不必要的锁来提高系统的性能。

标签:总结,加锁,java,获取,线程,自旋,操作,轻量级
From: https://blog.51cto.com/u_16209090/6959669

相关文章

  • 为了成为Java大牛,我决定手写个JVM~
    JVM对我们很多人来说就像个黑盒子,无从下手,但是又是我们JavaCoder不得不去深入研究的一门技术国内玩JVM的大牛很少,知名的就那么几个,而玩好JVM又教好JVM的人更是少之又少。今天给大家介绍其中一位,江湖人送外号道格牙的子牙老师。下面的时间,交给他。哈喽,我就是江湖人送外号[......
  • 反内卷 | Java程序员不可错过的10本书!
    大家好,我是飘渺。又到了一年一度的双11了,想必不少小伙伴打算趁此机会入手几本好书。在此我也为大家整理了一份书单,如果你还在纠结犹豫,不妨看看我为整理的这几本。PS:今年京东的图书活动大致如下:活动一“多买优惠”,即:1件7.5折,两件6.5折活动二:“满减”,即:每满100-50这两个活动结算的时......
  • 【技术总结】大数据开发模块化知识体系、学习路线及对应的资料推荐
    〇、概述1、常用网站 2、常用学习路线图极客时间:石墨文档:https://shimo.im/docs/anJWOliiPz0WEjY0/read 大数据课程大纲:https://w.1yb.co/LEAzlvV【即石墨文档】 马士兵教育:https://www.processon.com/view/link/6244466b5653bb072bcd241d#map 尚硅谷:http://www.atguigu.com/......
  • JavaSE--标识符与关键字
    一、标识符1、标识符可以标识那些:类名、方法名、变量名、接口名、常量名......注意:主方法中main也是标识符,但是不能修改,因为main时sun公司规定的固定的2、标识符命名规则标识符只能由数字、字母、下划线、美元符号$、中文,不能有其他符号不能以数字开头关键字不能做为标识......
  • 《介绍篇》c#为什么能在桌面应用上战胜java
    参考链接:https://blog.csdn.net/weixin_39539761/article/details/114158817参考链接:http://it.cha138.com/shida/show-5705680.htmljava语言确实是一种比较不错的语言。相比于C,C++等语言来说,java是移植性和语言的形式上都是非常优秀,尤其是Web开发和Android移动应用开发方面,可以......
  • Java 诊断工具 Arthas 教程学习笔记
    Java诊断工具Arthas教程学习笔记 Java诊断利器Arthas,是阿里的一款开源工具。Github-alibaba/arthas 上可以看到它的介绍。了解它,主要是最近对分析Java错误堆栈比较感兴趣,机缘巧合看到了它。本文记录的内容,就是基于它官网的文档摘抄的,涉及的截图可能由于篇幅有限,不是......
  • Java面试题 P59:微服务篇:分布式系统理论-CAP和BASE
           ......
  • Java反射与自定义注解实现不同Bean属性映射的高效解决方案
    假设现有一个叫user的bean,里面有username、passsword、sex、createTime这四个属性,需要实现一个功能,把其它bean里的字段分别映射到user里的username、passsword、sex、createTime这几个属性上,然后可以根据其它的bean的实例自动转化为user的实例(填充对应属性值),下面是代码实现。Java......
  • 上位机_WPF系列总结(触发器)
    当达到了出发的条件,执行设定的响应,可以是样式、数据变化、动画等。触发器的类型有:Trigger:检测依赖属性的变化,触发器生效<Window.Resources><Stylex:Key="TestStyle"TargetType="Button"><Style.Triggers><TriggerProperty="IsMou......
  • Java获取字符串中首次出现非数字的位置
    /***获取字符串中首次出现非数字的位置*@paramvalue字符串内容*@return首次出现非数字的位置,若无非数字,则返回-1*/publicstaticintgetFirstNonDigitPosInString(Stringvalue){intfirstNonDigitPos=-1;if(TextUtils.i......