🎈专栏链接:多线程相关知识详解
目录
一.什么是死锁以及死锁的成因
Ⅰ.一个线程一把锁
Ⅱ.两个线程两把锁
Ⅲ.多个线程多把锁
二.死锁的解决方案
一.什么是死锁以及死锁的成因
死锁是一个线程加上锁了之后,解不开了
在多线程编程中,我们为了防止多线程竞争共享资源而导致数据错乱,都会在操作共享资源之前加上互斥锁,只有成功获得到锁的线程,才能操作共享资源,获取不到锁的线程就只能等待,直到锁被释放。
死锁的成因:
Ⅰ.一个线程一把锁
线程连续加锁两次,如果这个锁是不可重入锁,那必然就是死锁,synchronized是可重入锁,没有这个问题
Ⅱ.两个线程两把锁
当两个线程为了保护两个不同的共享资源而使用了两个互斥锁,那么这两个互斥锁应用不当的时候,可能会造成两个线程都在等待对方释放锁,在没有外力的作用下,这些线程会一直相互等待,就没办法继续运行,这种情况就是发生了死锁。
例如:家里门的钥匙锁车里,而车钥匙锁家里了
Ⅲ.多个线程多把锁
对于该问题有一个典型的例子:哲学家就餐问题
一张桌子,上面有一碗意大利面,和5根分散开来的筷子,桌子旁边坐着5名哲学家
每个哲学家只做两件事情:
1.思考人生,啥也不干(线程阻塞)
2.吃面条,先拿左手边的筷子,再拿右手边的筷子,吃一会儿放回筷子
啥时候做第一件事啥时候做第二件事都是不确定的(线程随机调度)
在大部分情况这个是不会出现问题的
但是如果当5个哲学家同时要吃面条,拿起了左手边的筷子,就会出现死锁,因为右手边的筷子被其他哲学家拿走了
出现死锁的四个必要条件:
1.互斥使用.锁A被线程1占用,线程2就用不了
2.不可抢占.锁A被线程1占用,线程2不能把锁A抢过来, 除非线程1主动释放锁
3.请求和保持.有多把锁,线程1拿到锁A后,不想释放锁A,还想拿到一个锁B
4.循环等待.线程1 等待 线程2释放锁,线程2 要释放锁得等待线程3 来释放锁,线程3 释放锁得等待线程1 释放锁
二.死锁的解决方案
在上面死锁的四个必要条件当中,第一条和第二条是锁的必要条件 无法改变,
第三条的话,如果需求场景符合获取锁B的时候先释放了锁A那也是可行的,但是这个方法不普适
解决死锁的突破口就在第四条,约定好加锁的顺序,就可以打破循环等待
线程t1的加锁顺序:locker1 , locker2. 线程t2的加锁顺序:locker2 , locker1. 这样就导致了循环等待,如果调整了顺序,就可以避免循环等待
例如:给锁编号,约定加多个锁的时候,必须先加编号小的锁,后加编号大的锁
让这些哲学家约定所获取的两把锁必须先获取编号小的,再获取编号大的(而不是先拿左手的再拿右手的)
这样子的话,从第一个哲学家开始拿编号小的筷子,直到第5个哲学家,他得先拿编号小的筷子1,才能拿编号大的筷子5,但是筷子1已经被第一个哲学家拿走了,这样的话第5个哲学家就会进入阻塞等待状态,等待第1个哲学家释放锁,这样就不会造成死锁问题
有一个更普适的解决方案 --- "银行家算法"(把所有的资源统一进行统筹分配),但不太适合实际开发