1. 乐观锁VS悲观锁

锁得实现者,预测接下来锁冲突的概率(同个线程针对同一个对象加锁,产生阻塞等待)大不大,根据这个冲突的概率,来决定接下来该怎么做~

  • 乐观锁:看名字就是比较乐观,预测冲突概率不大,所以做的工作会比较少,效率会高一点(并不绝对)
  • 悲观锁:就是预测冲突概率比较大,所做的工作比较多,效率会低一点(并不绝对)

乐观锁:假设数据一般情况下不会发生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则返回用户错误的信息,让用户决定如何去做
悲观锁:总是假设最坏情况,每次对数据都会进行修改操作,所以在每次拿数据的时候都会上锁,这样其他线程想操作这个数据就会阻塞等待

2. 轻量级锁VS重量级锁

synchronized 并不仅仅是对 mutex 进行封装,内部还做了很多其他的工作
synchronized 开始是一个轻量级锁,如果锁冲突比较严重,就会 变成重量级锁

  • 轻量级锁:加锁解锁,过程更快更高效,加锁机制尽可能不使用mutex,而是在用户态代码完成,实在搞不定,再使用mutex

    • 少量的内核用户态切换
    • 不太容易引发线程的调度
  • 重量级锁:加锁解锁,过程更慢更低效,加锁机制重度依赖OS提供的mutex
    • 大量的内核用户态切换
    • 很容易引发线程的调度

3. 自旋锁VS挂机等待锁

自旋锁(轻量级锁的一种典型实现)

  • 按照之前的方式,线程在抢锁失败后进入阻塞状态,放弃CPU,需要很久才能再次调度,然后实际上,在大多数情况下,虽然抢锁失败,但过不了多久,锁就会被释放。所以就没必要放弃CPU,这个时候就可以使用自旋锁来处理这种情况。
  • 优点:如果获取锁失败后,就会一直尝试获取,无限循环,一直等到获取锁位置!一旦锁被其他线程释放,就会第一时间获取到锁!
  • 缺点:会造成忙等,消耗CPU资源

挂起等待锁(重量级锁的一种典型实现)

  • 挂起等待锁,当抢锁失败后,就放弃了CPU,进入到等待队列,这个时候如果其他线程释放锁之后,就会被唤醒,但是不能第一时间拿到资源的,可能就会需要很久才能获取到锁!

举两个例子:
例子1(自旋锁):假设你向女神表白,但是你被发了好人卡~,但是你仍旧每天锲而不舍的,向女神发送早安晚安,一旦哪一天,女神和男朋友分手了,此时你就有很大机会,把锁给加上!
例子2(挂起等待所):表白失败后,你就不找女神了,或许在女神分手后某一天,想到了你,就会主动来找你,这个时候你的机会也来了,但是不一样你就是找的第一个~

针对上述三组策略:提出个疑问? synchronized 属于哪一种
synchronized 既是悲观锁,也是乐观锁;既是轻量级锁,也是重量级锁;轻量级锁基于自旋锁实现,重量级锁基于挂起等待锁实现~
具体synchronized 会根据当前锁竞争的激烈程度,自适应

4. 互斥锁VS读写锁

synchronized就是互斥锁,加锁就是单纯的加锁,没有更细化的区分。
但是读写锁,能够把 读 和 写 两种加锁区分开来
在多线程中,数据的读取不会产生线程安全的问题。但是数据的写入互相之间和读之间都需要进行互斥。 此时考虑到一个问题,如果两种情况下都使用同一个锁,就会产生极大的性能消耗,所以读写锁就产生了!

一个线程对于数据的访问,就要是两种:读操作和写操作

  • 两个线程都只是进行读操作,没有线程安全问题
  • 两个线程都要进行写操作,就会有线程安全问题
  • 一个线程读另一个线程写,就会有线程安全问题

所以针对上面读和写操作区别,Java标准库提供了 ReentrantReadWriteLock 类,实现了读写锁

  • ReentrantReadWriteLock.ReadLock 类 表示 一个读锁,这个对象提供了 lock/ unlock 进行加锁解锁
  • ReentrantReadWriteLock.WriteLock 类 表示 一个写锁,这个对象也提供了 lock/unlock 方法进行加锁解锁

其中:

  • 读加锁和读加锁之间,不互斥
  • 读加锁和写加锁之间,互斥
  • 写加锁和写加锁之间,互斥

读写锁特别适合 “频繁都,不频繁写” 的场景中

5. 可重入锁VS不可重入锁

可重入锁:就是允许同一个线程多次获取同一把锁
不可重入锁:一个线程,针对同一把锁,连续加锁两次,发生死锁

Object locker = new Object();
synchronized(locker){synchronized(locker){// ...    }
}

上述代码中,不会产生死锁,因为 synchronized 是可重入锁!在加锁的时候,判断当前申请锁的线程是不是已经是锁的拥有者,如果是,就直接放行了~


下面介绍一些死锁的情况:

  • 1.一个线程,一把锁,可重入锁没有事,不可重入锁就会出现死锁
Object locker = new Object();
synchronized(locker){synchronized(locker){// ...    }
}

  • 2.两个线程,两把锁,即使是可重入锁,也会死锁
Object locker1 = new Object();
Object locker2 = new Object();// t1 线程
synchronized(locker1){synchronized(locker2){//...}
}// t2 线程
synchronized(locker2){synchronized(locker1){//...}
}

上述代码,t1 线程获取 locker1 锁 ,t2 获取 locker2 锁,此时 t1要获取locker2锁时获取不到,要等t2释放以后,而t2要获取 locker1锁 之后,也必须等 t1 释放 locker1,此时就造成了死锁~~


  • 3.N个线程,M把锁
    这里就有个经典的哲学家,就餐问题

说到这里,我们介绍下产生死锁的四个必要条件:

  • 互斥使用:一个线程拿到一把锁之后,另一个线程不能使用
  • 不可抢占:一个线程拿到锁,只能自己主动释放,不能被其他线程强行占用
  • 请求和保持:一个线程在拿到锁之后,不释放,同时又在申请获取其他的锁
  • 循环等待:上述例子,逻辑依赖循环的

如何避免死锁~只要破除其中的任意一个条件就行,这里介绍 破坏循环等待这个条件!

  • 就是针对锁进行编号,如果需要同时获取多把锁,约定加锁顺序,务必是先对小的编号进行加锁,后对大的编号加锁!

// 伪代码
Object locker1 = new Object();
Object locker2 = new Object();// 线程A执行下面代码逻辑
synchronized(locker1){synchronized(locker2){//...}
}// 线程B执行下面代码逻辑
synchronized(locker1){synchronized(locker2){//...}
}

此时就不会造成循环等待现象~

6. 公平锁VS非公平锁

假设有三个线程对一个数据进行操作,线程A先获得锁,线程B也在申请获取锁,线程C也在申请获取锁。线程B在线程C之前申请获取锁

  • 公平锁:就是当线程A释放锁之后,线程B先线程C之前申请获取锁,此时就是线程B先获得,然后等线程B释放以后,线程C再获得锁
  • 非公平锁:就是当线程A释放锁,线程B和线程C获取锁的先后 ,是不确定的,两个可能先于对方

7. synchronized

系统对于线程的调度是随机的,自带的synchronized这个锁是非公平的~
要想实现公平锁需要在 synchronized 的基础上,加上队列,来记录这些加锁线程的顺序

synchronized的特点:

  • 既是悲观锁,也是乐观锁
  • 既是轻量级锁,也是重量级锁
  • 轻量级锁基于自旋锁实现,重量级锁基于挂机等待锁
  • 不是读写锁
  • 是可重入锁
  • 是非公平锁
  • 是互斥锁

【十一】一文带你迅速掌握锁策略相关推荐

  1. 爬虫入门经典(十一) | 一文带你爬取传统古诗词(超级简单!)

      大家好,我是不温卜火,是一名计算机学院大数据专业大三的学生,昵称来源于成语-不温不火,本意是希望自己性情温和.作为一名互联网行业的小白,博主写博客一方面是为了记录自己的学习过程,另一方面是总结自己 ...

  2. 一文带你理解 WireGuard 路由策略

    公众号关注 「奇妙的 Linux 世界」 设为「星标」,每天带你玩转 Linux ! ❝ 原文链接

  3. 教妹学Java(二十一):一文带你了解面向对象编程的所有概念

    你好呀,我是沉默王二,是<Web 全栈开发进阶之路>的作者,CSDN 的博客之星.<教妹学 Java>是一套非常有趣的付费专栏,除了继续保持幽默风趣的行风风格,我还力求把每一个 ...

  4. 一文带你深入理解JVM内存模型

    一文带你深入理解JVM内存模型 一.JAVA的并发模型 共享内存模型 在共享内存的并发模型里面,线程之间共享程序的公共状态,线程之间通过读写内存中公共状态来进行隐式通信 该内存指的是主内存,实际上是物 ...

  5. 还不会ts?一文带你打开ts的大门

    一文带你打开ts的大门 序言 一.什么是TypeScript? 1.编程语言的类型 2.TypeScript究竟是什么? 二.为什么要学习TypeScript? 1.程序更容易理解 2.效率更高 3. ...

  6. 一文带你理解Java中Lock的实现原理

    转载自   一文带你理解Java中Lock的实现原理 当多个线程需要访问某个公共资源的时候,我们知道需要通过加锁来保证资源的访问不会出问题.java提供了两种方式来加锁,一种是关键字:synchron ...

  7. 超详细!一文带你了解 LVS 负载均衡集群!

    作者 | JackTian 来源 | 杰哥的IT之旅(ID:Jake_Internet) 前言 如今,在各种互联网应用中,随着站点对硬件性能.响应速度.服务稳定性.数据可靠性等要求也越来越高,单台服务 ...

  8. 一文带你搞懂C#多线程的5种写法

    一文带你搞懂C#多线程的5种写法 1.简介 超长警告! 在学习本篇文章前你需要学习的相关知识: 线程基本知识 此篇文章简单总结了C#中主要的多线程实现方法,包括: Thread 线程 ThreadPo ...

  9. 进程、线程与协程傻傻分不清?一文带你吃透!

    目录 前言 内容大纲 进程 什么是进程 进程的控制结构 进程的状态 进程的上下文切换 线程 什么是线程 线程与进程的对比 线程的上下文切换 线程的模型 调度 调度原则 调度算法 好文推荐 前言 欢迎来 ...

最新文章

  1. linux中deb怎样安装
  2. 一般判五年几年能出来_判刑五年能减刑多少年
  3. 原来流行也可以变成怀旧!
  4. PHP获取当前页面的URL
  5. 音视频技术开发周刊 | 233
  6. namenode启动不了以及datanode启动不了的解决办法
  7. myjdbc链接数据库.增删改查
  8. 网络数据库的复制和同步(一)
  9. 算法-无向图(深度优先搜索和广度优先搜索)
  10. 大学英语(第四册)复习(原文及全文翻译)——Unit 5 - TO LIE OR NOT TOLIE—THE DOCTOR‘S DILEMMA(撒谎还是不撒谎——医生的难题)
  11. recovery 工作流程
  12. 计算机类核心期刊排名(国内)
  13. Android使用addr2line定位native崩溃堆栈
  14. 教你如何学模电——三极管篇
  15. STC89C52RC的P4口的应用问题
  16. IOS 跨域问题分析和处理
  17. 提高编程效率的5大VS Code插件
  18. Twitter整合营销的方法技巧
  19. 美国证监会给区块链股票降温
  20. (其他2)VideoPlayer 介绍

热门文章

  1. 回文日期 php,c语言程序实例大全,220个详细程序源代码
  2. 粉丝经济大爆发居然真的来了
  3. 苹果手机投影_投影仪连手机的两种方式介绍
  4. 神州租车还有这些新玩法?打造了行业新标杆
  5. 二维数组中的查找(两种解法,各有千秋)
  6. 黑盒测试、白盒测试与灰盒测试方法
  7. 《电子元器件的可靠性》——2.5节威布尔分布及其概率纸的结构和用法
  8. 解决QQ小游戏、微信小游戏 getLaunchOptionsSync()获取的数据为旧数据
  9. 经典数学问题——三门问题(数据分析面试题)
  10. tmall.item.add.simpleschema.get( 天猫发布商品规则获取 )