作为本系列的最后一篇,我们今天来盘点一下前面这些章节中涉及的锁:

作为高并发的核心主题之一,各种各样的锁伴随我们整个课程 ,现在我们就来梳理一下到底有多少种锁。

需要注意的是,这些锁不是简单的包含与被包含的关系,也不是并列关系,而是从不同的角度看就有不同类型的锁。

1 悲观锁和乐观锁

我们接触的第一个锁是synchronized,其思想就是只要可能发生访问冲突的地方就先保护起来,因此这是一种悲观思想,即认为写多读少,遇到并发写的可能性高,每次去拿数据的时候都认为其他线程会修改,所以每次读写数据都会认为其他线程会修改,所以每次读写数据时都会上锁。其他线程想要读写这个数据时,会被这个线程block,直到这个线程释放锁然后其他线程获取到锁。这就是悲观锁synchronized修饰的方法和方法块、ReentrantLock,都是悲观锁。

但实际上很多时候是读多写少,而读的时候因为不改变原始文件,因此不存在访问冲突,如果此时仍然加锁再读,效率就太低了。这时候可以先读,如果发生了冲突再特别处理就行了。这就是乐观锁的思想,CAS就是典型的乐观锁。

举个防火的例子,悲观锁的策略是将一切可能发生火灾的地方全部防范。而乐观锁的策略是家里备着灭火器,平时该干嘛干嘛,哪里发生火灾就灭哪里。

2 自旋锁

自旋的意思就是当前线程一直在等待,对应的代码就是:

for(;;){
..执行代码..
}

在工程里写这种代码是非常危险的,可能导致程序进入死循环,但是在多线程里,我们大量看到类似的情况,这意味执行代码的条件要设计非常严谨,各种情况都要考虑周全。

另外,for循环不是一直执行的,否则会一直占有CPU资源,事实上,这里是根据CPU的时间片来按照一定的间隔执行的,因此在执行代码里经常会看到Unsafe类的方法。

自旋锁的优点: 避免了线程切换的开销。挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给Java虚拟机的并发性能带来了很大的压力。

自旋锁的缺点: 占用处理器的时间,如果占用的时间很长,会白白消耗处理器资源,而不会做任何有价值的工作,带来性能的浪费。因此自旋等待的时间必须有一定的限度,如果自旋超过了限定的次数仍然没有成功获得锁,就应当使用传统的方式去挂起线程。

自旋次数默认值:一般是10次,可以使用参数-XX:PreBlockSpin来自行更改。现在很多操作都使用自适应自旋来优化, 自适应意味着自旋的时间不再是固定的,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定的。有了自适应自旋,随着程序运行时间的增长及性能监控信息的不断完善,虚拟机对程序锁的状态预测就会越来越精准。

3 可重入锁

可重入锁(递归锁),指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。 在 JAVA 里 ReentrantLock 和 synchronized 都是可重入锁。

4 读锁和写锁

为了提高性能,Java 提供了读写锁,在读的地方使用读锁,在写的地方使用写锁,灵活控制。如果没有写锁的情况下,读是无阻塞的,在一定程度上提高了程序的执行效率。 读写锁分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥由 jvm 控制的,程序员只需要上好相应的锁要求代码只读数据,可以很多人同时读,但不能同时写,可上读锁代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁Java 中 读 写 锁 有 个 接 口 java.util.concurrent.locks.ReadWriteLock , 也 有 具 体 的 实 现 ReentrantReadWriteLock。

5 独占锁和共享锁

独占锁又称为互斥锁、同步锁。

独占锁模式下,每次只能有一个线程能持有锁。独占锁是一种悲观保守的加锁策略,它避免了读/读冲突,如果某个只读线程获取锁,则其他读线程都只能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性。ReentrantLock 就是以独占方式实现的互斥锁。

共享锁 共享锁则允许多个线程同时获取锁,并发访问 共享资源,如:ReadWriteLock。共享锁则是一种乐观锁,它放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源。java 的并发包中提供了 ReadWriteLock,读-写锁。它允许一个资源可以被多个读操作访问,或者被一个 写操作访问,但两者不能同时进行

6 锁状态

锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。这个主要是针对synchronized优化而产生的。

重量级锁(Mutex Lock) 这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为“重量级锁”。Synchronized 是通过对象内部的做监视器锁(monitor)实现。监视器锁是依赖于底层的操作系统的 Mutex Lock 来实现,而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized 效率低的原因。

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

偏向锁 引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次 CAS 原子指令,而偏向锁只需要在置换ThreadID 的时候依赖一次 CAS 原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的 CAS 原子指令的性能消耗)。 轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能。

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

7 分段锁

分段锁主要针对ConcurrentHashMap的,为了兼顾性能和线程安全,在JDK7中,将ConcurrentHashMap分成若干独立的段,在JDK8中对ConcurrentHashMap做了很多优化,但是基本思想仍然是分段的。

8 死锁和活锁

synchronized同步锁,虽然能解决线程安全的问题,但是如果使用不当,就可能导致死锁,也即请求被阻塞而一直无法返回。

除了死锁,还有个活锁的情况,很多人只听过死锁,但是没听过活锁。

死锁: 一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象。死锁的四个条件缺一不可:互斥、占有且等待、不可抢占、循环等待。

活锁: 活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。也就是“生不如死”的状态,不过处于活锁的实体是在不断的改变状态,活锁有可能自行解开。

9 公平和非公平锁

公平锁 多个线程按照申请锁的顺序来获取锁。在并发环境中,每个线程会先查看此锁维护的等待队列,如果当前等待队列为空,则占有锁,如果等待队列不为空,则加入到等待队列的末尾,按照FIFO的原则从队列中拿到线程,然后占有锁。

非公平锁: 线程尝试获取锁,如果获取不到,则再采用公平锁的方式。多个线程获取锁的顺序,不是按照先到先得的顺序,有可能后申请锁的线程比先申请的线程优先获取锁。

非公平锁的优点: 非公平锁的性能高于公平锁。缺点: 有可能造成线程饥饿(某个线程很长一段时间获取不到锁)

Java中的非公平锁:synchronized是非公平锁,ReentrantLock通过构造函数指定该锁是公平的还是非公平的,默认是非公平的。

10 锁粗化和锁消除

这两种都是锁优化技术。

锁粗化: 如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作都是出现在循环体体之中,就算真的没有线程竞争,频繁地进行互斥同步操作将会导致不必要的性能损耗,所以就采取了一种方案:把加锁的范围扩展(粗化)到整个操作序列的外部,这样加锁解锁的频率就会大大降低,从而减少了性能损耗。

锁消除: 就是把锁干掉。当Java虚拟机运行时发现有些共享数据不会被线程竞争时就可以进行锁消除。那如何判断共享数据不会被线程竞争?

利用逃逸分析技术:分析对象的作用域,如果对象在A方法中定义后,被作为参数传递到B方法中,则称为方法逃逸;如果被其他线程访问,则称为线程逃逸。

在堆上的某个数据不会逃逸出去被其他线程访问到,就可以把它当作栈上数据对待,认为它是线程私有的,同步加锁就不需要了。

30.盘点各种各样的锁相关推荐

  1. 终于肝了30篇高并发-学习贵在坚持

    到今天为止终于将多线程给过了一遍,在发第一篇之前,已经花了比较多的时间做笔记,最近只是将笔记重新分析了一遍,然后拆分成合适的文章逐步完善和发布. 高并发与多线程是个非常广阔的大海,而且很多内容学习难度 ...

  2. 锁到底是一种怎样的存在?

     作者:厚朴 juejin.cn/post/6955318854889242638 锁到底是一种怎样的存在? 随着业务的发展与用户量的增加,高并发问题往往成为程序员不得不面对与处理的一个很棘手的问题, ...

  3. 结合java中的锁聊聊锁的本质

    在操作系统里面,也会遇到什么信号量.互斥量,然后说利用互斥量.信号量可以实现锁的功能,而操作系统提供的原语有又mutex锁 在学习数据库的时候,什么表锁.行锁.读锁.写锁.排它锁.意向锁.meta锁等 ...

  4. java自适应table_【进阶之路】包罗万象——JAVA中的锁

    导言 大家好,我是练习java两年半时间的南橘,下面是我的微信,需要之前的导图或者想互相交流经验的小伙伴可以一起互相交流哦. 在Java中,我们能接触到各种各样的锁,而每种锁因其特性的不同,在不同的的 ...

  5. 【高并发】高并发分布式锁架构解密,不是所有的锁都是分布式锁!!

    来自:冰河技术 写在前面 最近,很多小伙伴留言说,在学习高并发编程时,不太明白分布式锁是用来解决什么问题的,还有不少小伙伴甚至连分布式锁是什么都不太明白.明明在生产环境上使用了自己开发的分布式锁,为什 ...

  6. MySql各种锁机制的学习

    加锁的目的: 数据库是一个多用户使用的共享资源.当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况.若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性.锁 ...

  7. mysql什么情况会加意向锁_MySQL中的锁4-插入意向锁和自增锁

    插入意向锁(Insert Intention Lock) 插入意向锁本质上可以看成是一个Gap Lock 普通的Gap Lock 不允许 在 (上一条记录,本记录) 范围内插入数据 插入意向锁Gap ...

  8. Java锁详解:“独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁+线程锁”

    在Java并发场景中,会涉及到各种各样的锁如公平锁,乐观锁,悲观锁等等,这篇文章介绍各种锁的分类: 公平锁/非公平锁 可重入锁 独享锁/共享锁 乐观锁/悲观锁 分段锁 自旋锁 线程锁 乐观锁 VS 悲 ...

  9. Java并发编程—锁的基本概念

    在学习或者使用Java的过程中进程会遇到各种各样的锁的概念:公平锁.非公平锁.自旋锁.可重入锁.偏向锁.轻量级锁.重量级锁.读写锁.互斥锁等待.下边总结了对各种锁的解释 1.公平锁/非公平锁 公平锁是 ...

最新文章

  1. BZOJ1880:[SDOI2009]Elaxia的路线(最短路,拓扑排序)
  2. unity3d php js,【Unity开发】unity3d中的http通信?post/get
  3. 使用tinymini210开发远程的温度监控系统,实现C#和安卓客户端显示数据和控制
  4. 如何在Ubuntu系统下安装JDK
  5. 教程,word导出为pdf既要书签和链接又要高清图片
  6. H3CSE园区-SSH
  7. 交换机端口镜像配置大全【汇集22个各种品牌交换机】
  8. java 截位法保留小数_资料分析——截位法
  9. 宽带远程服务器无响应,宽带拨号上网服务器无响应是解决方法(图文)
  10. git 设置单个文件上传大小
  11. 计算机高级培训教师感言,教师感言,句句经典
  12. Redhat7安装HDS存储多路径软件
  13. 正则表达式在一个字符串上多次搜索、正则表达式匹配书名等
  14. okcc和vos3000呼叫中心两者间有什么作用要一起使用吗?
  15. javascript输入某年月某日,判断是这一年的多少天
  16. Pyecharts 静态图片输出ppt中动态图表
  17. 超强数据恢复软件,丢失文件无损找回! 值得收藏!
  18. 【202211】国内镜像源地址
  19. 2022-2-23 王爽《汇编语言》实验二
  20. WPF编程基础入门 ——— 第二章 XAML

热门文章

  1. 《Arduino》开发 TFT_eSPI-master 库 之用 ESP32 读取SD卡上的图片并显示在1.14IPS屏幕上
  2. ChatGPT 如何改变高等教育
  3. 使用poi-tl导出动态word模板,包含折线图、柱状图、饼状图
  4. Java--->编程题之水仙花
  5. SAP GUI 740 windows 免费下载
  6. vmware虚拟机Linux系统( CentOS7)中英文输入法及切换方法
  7. Crypto Wednesday No.20 :一起聊聊隐私币
  8. 八 计算机组成与体系结构
  9. CMMI认证唯一查询官网
  10. 用Fragment来代替过期的Tabhost和ActivityGroup