多线程中的死锁、活锁、无锁、饥饿
充分理解多线程可能产生异常阻塞的场景,有利于我们并发编程时更加详尽的考虑问题。并发编程的核心在于维护和处理多线程,理解并发中存在的各种异常,死锁、活锁、无锁、饥饿都是可能让多线程异常阻塞的场景。
前言
充分理解多线程可能产生异常阻塞的场景,有利于我们并发编程时更加详尽的考虑问题
死锁、活锁、无锁、饥饿都是可能让多线程异常阻塞的场景
死锁
死锁是多线程中最差的一种情况,多个线程相互占用对方的资源的锁,又相互等对方释放锁。
这些线程则一直处理阻塞的假死状态,形成死锁。
举例:
- 假设工具库只有一个锤子和起子。
- A 要做一件事情,需要先用锤子,再用起子,他先拿起来锤子。
- B 同时也要做一件事情,需要先用起子,再用锤子,他先拿起了起子。
- A 锤子用完了,准备用起子,事情没做完前,A 不会把锤子放回工具库,但是现在没有起子,A 就等 B 把起子放回去。
- B 起子用完了,准备用锤子,事情没做完前,B 也不会把起子放回工具库,但是现在没有锤子,B 也在等 A 把锤子放回去。
- 于是,A 和 B 都在等待对方放回去。
产生条件
- 互斥条件:线程在某一时间内独占资源(A 拿了锤子,B 没得用)
- 请求与保持条件:线程因请求资源而阻塞时(等待资源锁),对已获得的资源保持不放(事不做完 A 不把锤子放回去)
- 不剥夺条件:线程已获得资源,在末使用完之前,不能强行剥夺(事不做完 A 的锤子谁也拿不到)
- 循环等待条件:线程之间形成一种头尾相接的循环等待资源关系(A 等 B 的起子,B 等 A 的锤子)
死锁解决办法
- 锁粒度提升:一次锁定获取所有需要的资源,比如例子中直接锁定整个工具箱,A 直接把箱子端走。
- 分布式锁或乐观锁机制的引入。
活锁
活锁和死锁相反,双方拿到资源,但是非常绅士的让别人使用。
你让我,我让你,最后多个线程都无法使用资源。
举例:
- A 和 B 都要用工具箱。
- A 拿到工具箱后发现 B 也要用工具箱,于是把工具箱递给 B。
- B 拿到工具箱后发现 A 也要用工具箱,于是把工具箱递给 A。
- 离离原上谱。
无锁
无锁,即没有对资源进行锁定,即所有的线程都能访问并修改同一个资源。
但时同时只有一个线程能修改成功。
无锁是一种锁实现形式,它可能产生问题,也有可能很效率(我们常说的乐观锁就是无锁实现)。
无锁的特点,修改操作在一个循环内进行,线程会不断的尝试修改共享资源,没有冲突就修改成功并退出(大名鼎鼎的 CAS 就是如此)。
因此,多个线程修改同一个值必定会有一个线程能修改成功,而其他修改失败的线程会不断重试直到修改成功。
当持续时间存在大量并发是,无锁可能产生性能问题,但是某些场合下确实非常高效。
饥饿
又称饿死。
由于某些原因,线程可能始终无法获得资源锁,导致一直无法执行。
比如我们常说的线程优先级概念,高优先级的线程总会优先抢占资源并执行。
饥饿和死锁不同,饥饿在经过一段时间后,终归会被执行,比如高优先级线程都执行结束了。
