建构网站西安,做信息图的网站,秦皇岛微信群,wordpress基本文档锁策略
一#xff0c;悲观锁 VS 乐观锁
悲观锁#xff1a;总是假设最坏的情况#xff0c;每次去拿数据的时候都认为别人会修改#xff0c;所以每次在拿数据的时候都会碰上锁#xff0c;这样别人想拿这个数据就会阻塞#xff0c;直到它拿到锁。
乐观锁#xff1a;假设…锁策略
一悲观锁 VS 乐观锁
悲观锁总是假设最坏的情况每次去拿数据的时候都认为别人会修改所以每次在拿数据的时候都会碰上锁这样别人想拿这个数据就会阻塞直到它拿到锁。
乐观锁假设数据一般不会发生修改等到别人真的要修改了就返回错误信息给用户让用户决定如何去做如果别人没有修改那其实就相当于没有上锁效率大大提高其实这和懒汉模式有点相似的思路。
二重量级锁 VS轻量级锁
我们先了解一下其他概念 锁的核⼼特性 “原⼦性”, 这样的机制追根溯源是 CPU 这样的硬件设备提供的如下图
CPU 提供了 “原子操作指令”.操作系统基于 CPU 的原⼦指令, 实现了 mutex 互斥锁.JVM 基于操作系统提供的互斥锁, 实现了 synchronized 和 ReentrantLock 等关键字和类. 重量级锁 加锁机制重度依赖了 操作系统 提供的 mutex大量的内核态用户态切换很容易引发线程的调度 这两个操作, 成本比较高. ⼀旦涉及到用户态和内核态的切换, 就意味着 “沧海桑田”. 轻量级锁 加锁机制尽可能不使用 mutex, ⽽是尽量在用户态代码完成. 实在搞不定了, 再使用mutex.少量的内核态用户态切换.不太容易引发线程调度. 我们举个例子 假如某天我们去银行办理业务工作人员给了我们两个选择 1直接将所有的材料交给他让他帮我们办理 2我们在窗口外就将该打印该填写的表都弄完然后再交给他 想一想我们应该选哪一个呢可能大多数人都会选第一个图省事。但是从效率的角度来讲第二个更加的高效为什么呢要知道去办理业务的可不止我们银行的工作人员拿到的不一定只有我们的一份材料所以他可能先将所有的材料都先整理在一块然后统一给办理。这样对于我们来说是不是没有我们将所有的东西弄好直接办理快呢。
三自旋锁 VS 挂起等待锁
自旋锁 按之前的⽅式线程在抢锁失败后进⼊阻塞状态放弃 CPU需要过很久才能再次被调度.但实际上, ⼤部分情况下虽然当前抢锁失败但过不了很久锁就会被释放。没必要就放弃 CPU. 这个时候就可以使⽤⾃旋锁来处理这样的问题 伪代码
1 while (抢锁(lock) 失败) {}如果获取锁失败, ⽴即再尝试获取锁, ⽆限循环, 直到获取到锁为⽌. 第⼀次获取锁失败, 第⼆次的尝试会在极短的时间内到来.⼀旦锁被其他线程释放, 就能第⼀时间获取到锁. 挂起等待锁 这个锁其实和自旋锁是有点相反的该锁在获取锁失败后会直接进入阻塞状态放弃CPU需要很久才能被调度这也就是之前我们最常用到的锁。 为了更好理解举个例子 想象⼀下, 去追求⼀个⼥神. 当男⽣向⼥神表⽩后, ⼥神说: 你是个好⼈, 但是我有男朋友了~~ 挂起等待锁: 陷⼊沉沦不能⾃拔… 过了很久很久之后, 突然⼥神发来消息, “咱俩要不试试?” (注意, 这个很⻓的时间间隔⾥, ⼥神可能已经换了好⼏个男票了). ⾃旋锁: 死⽪赖脸坚韧不拔. 仍然每天持续的和⼥神说早安晚安. ⼀旦⼥神和上⼀任分⼿, 那么就能⽴刻抓住机会上位.
四公平锁VS非公平锁
假设三个线程 A, B, C. A 先尝试获取锁, 获取成功. 然后 B 再尝试获取锁, 获取失败, 阻塞等待; 然后 C也尝试获取锁, C 也获取失败, 也阻塞等待. 当线程 A 释放锁的时候, 会发⽣啥呢? 公平锁: 遵守 “先来后到”. B ⽐ C 先来的. 当 A 释放锁的之后, B 就能先于 C 获取到锁. 非公平锁: 不遵守 “先来后到”. B 和 C 都有可能获取到锁. 举个例子 这就好⽐⼀群男⽣追同⼀个⼥神. 当⼥神和前任分⼿之后, 先来追⼥神的男⽣上位, 这就是公平锁; 如果是⼥神不按先后顺序挑⼀个⾃⼰看的顺眼的, 就是⾮公平锁. 非公平锁 其实上述的所谓公平和非公平并不是有什么特殊的含义其实有些含义可能与我们日常的想法还相反。这都是乌龟的屁股————“ 规定 ”。前人定的我们照着用就行了。 注意
操作系统内部的线程调度就可以视为是随机的. 如果不做任何额外的限制, 锁就是⾮公平锁. 如果要想实现公平锁, 就需要依赖额外的数据结构, 来记录线程们的先后顺序.公平锁和⾮公平锁没有好坏之分, 关键还是看适⽤场景synchronized是非公平锁。
五可重入锁 VS 不可重入锁
可重入锁的字面意思就是“ 可以重新进入的锁 ” 即允许同一线程多次获取同一把锁。 比如一个递归函数里有加锁操作递归过程中这个锁会阻塞自己吗如果不会那么这个锁就是可重入锁*出于这个原因也叫做递归锁。Java里面只要是以Reetrant 开头命名的锁都是可重入锁而且JDK 提供的所有现成的Lock 实现类包括synchronized 关键字都是可重入的但是Linux 系统提供的mutex 是不可重入锁。理解“把自己锁死”一个线程没有释放锁然后又尝试再次加锁。举个例子 synchronized就是不可重入锁。
六读写锁
多线程之间数据的读取方之间不会产⽣线程安全问题但数据的写⼊方互相之间以及和读者之间都需要进⾏互斥。如果两种场景下都⽤同⼀个锁就会产⽣极⼤的性能损耗这是由于我们的读操作本来就不需要加锁而如果平白一个锁就是多此一举很降低效率。所以读写锁因此⽽产⽣。复读者之间并不互斥⽽写者则要求与任何⼈互斥。 线程安全问题两个线程都只是读⼀个数据, 此时并没有线程安全问题. 直接并发的读取即可.两个线程都要写⼀个数据, 有线程安全问题.⼀个线程读另外⼀个线程写, 也有线程安全问题 为了解决上诉的线程安全问题. Java 标准库提供了 ReentrantReadWriteLock 类, 实现了读写锁ReentrantReadWriteLock.ReadLock 类表⽰⼀个读锁. 这个对象提供了 lock / unlock ⽅法 进⾏加锁解锁.ReentrantReadWriteLock.WriteLock 类表⽰⼀个写锁. 这个对象也提供了 lock / unlock⽅法进⾏加锁解锁. 注意读加锁和读加锁之间, 不互斥.写加锁和写加锁之间, 互斥.读加锁和写加锁之间, 互斥.只要是涉及到 “互斥”, 就会产⽣线程的挂起等待. ⼀旦线程挂起, 再次被唤醒就不知道隔了多久了.因此尽可能减少 “互斥” 的机会, 就是提⾼效率的重要途径.读写锁特别适合于 “频繁读, 不频繁写” 的场景中. (这样的场景其实也是⾮常⼴泛存在的如: 利用教务系统点名)synchronized 不是读写锁。
七相关题目 你是怎么理解乐观锁和悲观锁的具体怎么实现呢 悲观锁当多个线程同时访问同一个共享变量的时候总是认为会出现线程安全问题无论什么时候都按最坏的情况来处理每次都要加锁 乐观锁当多个线程同时访问同一个共享变量的时候认为大概率不会出现线程安全问题直接访问数据在访问的同时识别一下是否会出现线程安全问题。 2介绍下读写锁 读写锁就是将读操作和写操作分别加锁 读和读之间不互斥 写和写之间互斥 读和写之间互斥 读写锁多用在 “ 频繁读不频繁写”的场景中3什么是自旋锁为什么要使用自旋锁策略呢缺点是什么 自旋锁就是当一个线程加锁失败时再次重新尝试获取锁不会阻塞等待退出线程调度而是一直占据CPU一直在无限循环直到锁被释放能第一时间获取锁 优点没有放弃CPU资源自旋锁能在锁释放之后第一时间获取到锁在锁持有比较短的场景下非常有用。 缺点它会一直占据CPU资源4synchronized 是可重入锁么 是可重入锁。 可重入锁指的是连续两次加锁不会导致死锁。 实现的方式是在锁中记录该锁持有的线程身份以及一计数器如果发现当前加锁的线程就是持有锁的线程则直接计数自增