什么是死锁
死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
死锁的必要条件
- 1)互斥条件:某个资源某一时刻只能由一个线程占用
- 2)不可抢占条件:资源申请者不能从资源占有者手中抢夺资源
- 3)占有且申请:线程至少已经占有一个资源,但又去申请新的资源,新资源被占用,该线程会阻塞
- 4)循环等待:一个线程等待另外一个线程释放资源,另外一个线程又去等待,直到最后一个线程等待第一个线程释放资源,使得大家都被锁住
常见死锁
1)交叉锁
一个线程A 访问R1(获得锁R1),然后又访问R2;另一个先线程B 访问R2(锁住了R2),然后企图访问R1;这时线程A由于线程B已经锁住R2,它必须等待线程B释放R2才能继续,同样线程B要等线程A释放R1才能继续,这就死锁就产生了。
```java
private final Object R1 = new Object();
private final Object R2 = new Object();
//线程A
public void func1(){
synchronized (R1){
synchronized (R2){
//do something
}
}
}
//线程B
public void func2(){
synchronized (R2){
synchronized (R1){
//do something
}
}
}
2)内存不足
线程A和线程B都需要内存40MB,现在线程A已有10MB,还需要300MB;线程B已有10MB,还需要30MB,但是系统内存只剩余20MB,因此两个线程都在等对方释放内存来满足自己,死锁就产生了
3)一问一答的数据交换
服务器端(server)和客户端(client),server等待client的访问;client发送请求到server,等待接收数据;由于网络的原因,server错过了client的请求,因此server等待client的访问,client等待接收数据,产生死锁
4)死循环引起的死锁
有关开源,比如使用HashMap时,就会引起死锁
private static HashMap<String, String> map = new HashMap<>();
for(int i=0; i<2; i++){
new Thread(){
@Override
public void run() {
for(int i=0; i<Integer.MAX_VALUE; i++ ){
map.put(String.valueOf(i), String.valueOf(i));
}
}
}.start();
}
如何避免死锁
死锁的预防
- 1)允许某些线程同时访问某些资源
- 2)允许线程强行去夺取某些资源
- 3)允许实现资源预分配策略
- 4)线程按照编号申请资源
死锁的避免
安全序列: 如果系统按照这种序列去分配资源,每个线程都能够顺序完成自己的需求,这样序列就是安全序列,系统称之为安全状态。安全序列可能会有多个。
- 如果系统处于安全状态,就一定不会发生死锁;如果系统处于不安全状态,就"可能"会发生死锁。
解决哲学家就餐的死锁问题
有五位哲学家,身边各有一根筷子,如何才能让五个哲学家都就上餐,哲学家在就餐的时候必须保证左右两边筷子都可以使用
class Chopstick{
//key为筷子的编号,value是筷子的标识别
private static HashMap<Integer, Boolean> map = new HashMap<>();
static{
map.put(0, false);
map.put(1, false);
map.put(2, false);
map.put(3, false);
map.put(4, false);
}
public synchronized void getChopstick(){
String curName = Thread.currentThread().getName();
int leftChop = Integer.parseInt(curName); //左边筷子
int rightChop = (leftChop+1) % 5; //右边筷子
//判断左右两边的筷子是否都可用
while(map.get(leftChop) || map.get(rightChop)){
try {
this.wait();//表示两双筷子未能同时可用,哲学家陷入思考,线程阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
map.put(leftChop, true);
map.put(rightChop, true);
System.out.println("Thread "+curName+" got the chopsticks "+leftChop+" and "+rightChop);
}
public synchronized void freeChopstick(){
String curName = Thread.currentThread().getName();
int leftChop = Integer.parseInt(curName); //左边筷子
int rightChop = (leftChop+1) % 5; //右边筷子
map.put(leftChop, false);
map.put(rightChop, false);
this.notifyAll();
}
}
public class TestDemo8 {
public static void main(String[] args) {
Chopstick chopstick = new Chopstick();
for(int i=0; i<5; i++){
new Thread(String.valueOf(i)){
@Override
public void run() {
//当前线程获得两边筷子,准备就餐
// while(true){
chopstick.getChopstick();
System.out.println("Thread " +Thread.currentThread().getName()+" is eating");
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
chopstick.freeChopstick();
// }
}
}.start();
}
}
}