概述
1.互斥锁
1.1什么是互斥锁
互斥锁是一种独占锁,同一时间只有一个线程可以访问共享的数据资源。每个对象都应于一个可称为“互斥锁”的标记,来标记任何时刻只有一个线程访问这个对象
1.2为什么要添加互斥锁
一个进程中的多线程之间是共享系统资源的,多个线程同时操作一个对象,当一个线程的操作还没有结束,另一个线程也对他进行操作,导致出现错误,因此需要对被操作对象添加互斥锁,保证每个线程对该对象的操作都能得到正确的结果
1.3什么是多线程竞争,这么解决
线程不是独立的,数据是共享的,同时访问的时候会出现竞争的状态,造成数据混乱,这就是所谓的线程不安全
1.4竞争问题如何解决?
锁
好处:原子操作,将我们的一个线程从头到尾完整执行。
坏处:组织了多线程的并发执行,包含锁的某段代码实际上只能以单线程模式执行,效
率就大大的下降了
2. 致命问题——死锁
2.1什么是死锁,死锁的情况?
1.一种情况是线程A永远不释放锁,结果B一直拿不到锁,所以线程B就“死掉”了
2.第二种情况下,线程A拥有线程B需要的锁Y,同时线程B拥有线程A需要X,那
么这时候线程A/B互相依赖对方释放锁,于是二者都“死掉”了。
3.如果一个线程总是不能被调度,那么等待此线程结果的线程可能就死锁了。这种情况
叫做线程饥饿死锁。比如说非公平锁中,如果某些线程非常活跃,在高并发情况下这类
线程可能总是拿到锁,那么那些活跃度低的线程可能就一直拿不到锁,这样就发生了饥饿
2.2死锁产生的必要条件
(1) 互斥条件:一个资源每次只能被一个进程使用
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
其中一条不满足,就不会发生死锁。
2.3已经出现死锁如何解决
1.重启,代价太大
2.一次性撤销参与死锁的全部进程,剥夺全部资源
3.进程回退,很理想但是代价更大,需要很大的堆栈来记录每一步的变化从而实现回退
较为完善的方法其实就在预防上面!!!!!!!!
2.4如何预防死锁
1.尽可能的按照锁的使用规范请求锁,另外锁的请求粒度要小(不要在不需要锁的地方占用锁,锁不用了尽快释放);
2.在高级锁里面总是使用tryLock或者定时机制(就是指定获取锁超时的时间,如果时间到了还没有获取到锁那么就放弃
3.自旋锁
3.1什么是自旋锁,有什么特点
首先,我们要知道,线程在内核态与用户态之间切换是比较耗资源的。因此,这能要减少线程在阻塞、唤醒之间的切换
如果线程A持有线程B需要的锁,线程B觉得我好不容易来一趟,又要让我进入阻塞等待。不知下次几时才能分配到cpu资源,我能不能再等等,说不定线程A很快就完事。cpu说:可以,但有一个条件,我们不养闲线程,你必须做点事证明自己的用途。这样吧,你就来个自旋舞,给大伙助兴。于是,线程B就一直在自旋,直到线程A释放资源,它马上拿到锁资源,开始干活
优点
自旋锁可以减少CPU上下文的切换,对于占用锁的时间非常短或锁竞争不激烈的代码块来说性能大幅度提升,因为自旋的CPO耗时明显少于线程阻塞、挂起、再唤醒时两次CPU上下文切换所用的时间
缺点
在持有锁的线程占用锁时间过长或锁的竞争过于激烈时,线程在自旋过程中余长时间获取不到锁资源将列起CPU的浪费。所以在系统中有复杂锁依赖的情况下不适合采用自旋锁
4.读写锁
4.1什么是读写锁
顾名思义就是将读锁和写锁分离.读写锁其实也算是一种特殊的自旋锁。它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。在读写锁保持期间也是抢占失效的。
如果读写锁当前没有读者,也没有写者,那么写者可以立刻获得读写锁,否则它必须自旋在那里,直到没有任何写者或读者。如果读写锁没有写者,那么读者可以立即获得该读写锁,否则读者必须自旋在那里,直到写者释放该读写锁。
延展问题
自旋锁和互斥锁区别
互斥锁的起始原始开销要高于自旋锁,但是基本是一劳永逸,临界区持锁时间的大小并不会对互斥锁的开销造成影响,而自旋锁是死循环检测。加锁全程消耗pu,起始开销虽然低于互斥锁,但是随着持锁时间加锁的开销是线性增长。
乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量
2 实现:
常用的实现方式为CAS算法 : compare and swap(比较与交换)
CAS实现是由三个操作数来完成的, 1:读取的变量值 M 2:旧的预期值A 3:新的修改值B (以下简称)
CAS更新条件: 当CAS进行修改操作的时候, 当且仅当M=A,才会把M修改为B,否则什么都不做。(这个可能需要理解CAS的实现步骤,如果不懂建议学习下CAS实现原理).
缺点:
鱼和熊掌不可兼得,因为提高了性能,所以安全性肯定有所下降,这就导致一个经典的ABA问题出现了.
ABA问题:
CAS修改数据的时候 M初始读取的时候是A值 ,准备修改的时候M=A 符合CAS的条件了.但是这个A值有可能 是特务, 它首先变成了敌人D,然后又变成了友军A. 你说这样它还算是好人吗? 肯定不算. 所以ABA问题就是(A变成B 然后又变成A) . 为了解决这个问题.可以采用加个version版本号来解决.
悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。
实现:独占锁
缺点:
悲观锁由于每次使用前都会加锁,就导致在该锁没有被释放之前,是没办法做其他事情的,性能会非常差,
两者的使用场景:
CAS适用于写比较少的情况下(多读场景,冲突一般较少),synchronized适用于写比较多的情况下(多写场景,冲突一般较多)
试图递归地获得自旋锁必然会引起死锁
递归程序的持有实例在第二个实例循环,以试图获得相同自旋锁时,不会释放此自旋锁。
在递归程序中使用自旋锁应遵守下列策略:
1.递归程序决不能在持有自旋锁时调用它自己
2.决不能在递归调用时试图获得相同的自旋锁
此外如果一个进程已经将资源锁定,那么,即使其它申请这个资源的进程不停地疯狂“自旋”,也无法获得资源,从而进入死循环。
最后
以上就是勤劳花卷为你收集整理的互斥锁,死锁,自旋锁,读写锁,乐观,悲观1.互斥锁2. 致命问题——死锁3.自旋锁4.读写锁的全部内容,希望文章能够帮你解决互斥锁,死锁,自旋锁,读写锁,乐观,悲观1.互斥锁2. 致命问题——死锁3.自旋锁4.读写锁所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复