文章目录
- 什么是死锁
- 常见死锁情况❗️
- 死锁的必要条件❗️
- 如何避免死锁呢?
- CAS
- CAS中ABA问题
- 解决ABA问题
什么是死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象 。
常见死锁情况❗️
1.一个线程一把锁
//伪代码
synchronized(lock1){
synchronized(lock1){
...
}
...
}
我们以第一次加锁可以成功,当我们进行第二次加锁的时候就出现麻烦了,可以发现我们当前的锁对象已经被占用了,我们就会不断的去尝试加锁,此时就导致了死循环。
2、两个线程两把锁
//伪代码线程 t1
synchronized(lock1){
synchronized(lock2){
...
}
}
//伪代码线程 t2
synchronized(lock2){
synchronized(lock1){
...
}
}
假设有两个线程,线程t1和线程t2,当线程t1和t2同时开始执行,t1获取到了lock1锁对象,t2获取到lock2的锁对象,当t1去获取lock2时发现已经被占用,t2去获取lock1时发现也被占用,此时就发生了死锁。
3、N个线程M把锁
线程数量和锁的数量更多了,更容易死锁。
死锁的必要条件❗️
死锁的发生必须具备以下四个必要条件:
1.互斥使用:一个锁被一个线程占用后,其他线程使用不了(锁本质,保证原子性)。
2.不可抢占:一个锁被一个线程占用后,其他线程不能将锁抢占。
3.请求和保持:当一个线程占据多把锁后,除非显式释放锁,否则锁一直被该线程锁占用。
4.环路等待:多个线程等待关系闭环了,比如A等B,B等C,C等A
如何避免死锁呢?
死锁是一个比较严重的bug,实践中如何避免死锁呢?
一个简单的条件,破解循环等待~
针对锁进行编号,如果需要获取多把锁,约定加锁顺序,务必是先对小的编号加锁,后对编号大的枷锁。
//伪代码线程 t1
synchronized(lock1){
synchronized(lock2){
...
}
}
//伪代码线程 t2
synchronized(lock1){
synchronized(lock2){
...
}
}
CAS
CAS概念:CAS通过比较内存中的一个数据是否是预期值,如果是就将它修改成新值,如果不是则进行自旋(从新获取新值),重复比较的操作,直到某一刻内存值等于预期值再进行修改。
寄存器A的值如果比较内存M的值相等,那么就把寄存器B的值赋给M。CAS操作,是一条CPU指令,并非是一段代码!这一条指令能完成上述一段代码的的功能。
CAS中ABA问题
首先线程1进行读取共享变量A的值,由于种种原因线程挂起,在挂起时线程2执行把A的值改了,又改回来了,此后线程1挂起恢复,判断值一样(寄存器A和内存M),进行一系列操作,此时是会有bug的。
有些业务可能不需要关心中间过程,只要前后值一样就行,但是有些业务需求要求变量在中间过程不能被修改。
解决ABA问题
我们要想解决ABA问题我们需要引入版本号(当有修改操作版本号+1),此时再判断版本号相不相同,不相同则放弃此次CAS操作。