作者:opLW
java并发–死锁
目录
1.什么是死锁
2.出现死锁的原因
3.出现死锁四个必要条件
4.处理死锁的方法
4.死锁,活锁和饥饿的区别
- 什么是死锁:
- 死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
- 出现死锁的原因:
- 因系统资源不足导致的资源竞争
- 进程运行推进顺序不合适:请求和释放资源顺序不当
- 资源分配不当
- 出现死锁四个必要条件(必须同时具备):
- 资源互斥:一个资源只能被一个进程使用
- 请求与保持:当一个进程因请求资源而阻塞时候,保持已获得资源不放
- 不剥夺:进程已获得资源,在未使用完成之前,不能被其他进程强行剥夺
- 循环等待:若干进程之间形成一种头尾相接的循环等待资源关系
例子: lock(m1) lock(m2) unlock(m1) lock(m1) unlock(m2) unlock(m1)
假设有两个线程,线程1执行到lock(m1),lock(m2),unlock(m1),此时线程1持有锁m2,想要获取锁m1;线程2执行到lock(m1),此时线程2持有锁m1,想要获取锁m2。两个线程都拿着对方想要得到的锁,造成死锁。
-
处理死锁的方法:
- 办法 参考文章: https://blog.csdn.net/guaiguaihenguai/article/details/80303835
- 死锁预防和死锁避免的区别:
- 死锁预防 通过设置某些限制条件,去破坏死锁产生的四个必要条件的一个或多个。易实现,被广泛应用,但由于所施加的限制条件往往太严格,因而可能导致系统资源的利用率和吞吐率降低。(资源互斥:一个资源只能被一个进程使用,这个必要条件本身就不可能被破坏,所以主要破坏的是其他三个)
- 死锁避免 在资源的动态分配过程中,用某种方法防止系统进入不安全状态,从而避免死锁,而不需要事先采取各种限制措施去破坏产生死锁的四个必要条件。这种方法施加的限制条件较弱,但实现上有一定的困难。
- 区别 死锁预防是设法至少破坏产生死锁的四个必要条件之一,严格的防止死锁出现,而死锁避免则不那么严格的限制产生死锁的必要条件的存在,因为即使死锁的必要条件存在,也不一定发生死锁,死锁避免是在系统运行过程中注意避免死锁的最终发生。
- 记忆方法 死锁预防顾名思义就是提前做好工作,防范未然。所以对应破坏死锁产生的四个必要条件的一个或多个,对应一个静态的处理;而死锁避免则对应一个在发生过程中的动态避免。
- 死锁,活锁和饥饿的区别:
(1) 死锁例子:
public class Client {
private static final String a = "a";
private static final String b = "b";
public static void main(String[] args) {
Runnable r1 = new Runnable() {
@Override
public void run() {
synchronized (a) {
System.out.println(Thread.currentThread().getName() + "取得:" + a);
try{
Thread.sleep(2000); //确保另外一个线程已经启动
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b) {
System.out.println(Thread.currentThread().getName() + "取得:" + b);
}
}
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
synchronized (b) {
System.out.println(Thread.currentThread().getName() + "取得:" + b);
try{
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a) {
System.out.println(Thread.currentThread().getName() + "取得:" + a);
}
}
}
};
Thread t1 = new Thread(r1);
t1.setName("线程1");
Thread t2 = new Thread(r2);
t2.setName("线程2");
t1.start();
t2.start();
}
}
- 在synchronized (a) 时收到IDEA的提醒:Reports synchronized statements where the lock expression is a reference to a non-final field. Such statements are unlikely to have useful semantics, as different threads may be locking on different objects even when operating on the same object.(大概意思是说:锁的是一个non-final的引用,这些语句可能没有作用,因为不同的线程可能锁的是不同的对象)
- 原因: 没有被final修饰的引用,可能会被指向别的对象,如果在多线程中发生这种情况,就会使锁失效。以上是个人理解,详情可看 https://stackoverflow.com/questions/6910807/synchronization-of-non-final-field
(2)活锁例子:
public class Client {
static class Spoon {
private Diner owner;
public Spoon(Diner d) { owner = d; }
public Diner getOwner() { return owner; }
public synchronized void setOwner(Diner d) { owner = d; }
public synchronized void use() {
System.out.printf("%s has eaten!", owner.name);
}
}
static class Diner {
private String name;
private boolean isHungry;
public Diner(String n) { name = n; isHungry = true; }
public String getName() { return name; }
public boolean isHungry() { return isHungry; }
public void eatWith(Spoon spoon, Diner spouse) {
while (isHungry) {
// Don't have the spoon, so wait patiently for spouse.
if (spoon.owner != this) {
try { Thread.sleep(1); }
catch(InterruptedException e) { continue; }
continue;
}
// If spouse is hungry, insist upon passing the spoon.
if (spouse.isHungry()) {
System.out.printf(
"%s: You eat first my darling %s!%n",
name, spouse.getName());
spoon.setOwner(spouse);
continue;
}
// Spouse wasn't hungry, so finally eat
spoon.use();
isHungry = false;
System.out.printf(
"%s: I am stuffed, my darling %s!%n",
name, spouse.getName());
spoon.setOwner(spouse);
}
}
}
public static void main(String[] args) {
final Diner husband = new Diner("Bob");
final Diner wife = new Diner("Alice");
final Spoon s = new Spoon(husband);
new Thread(new Runnable() {
public void run() { husband.eatWith(s, wife); }
}).start();
new Thread(new Runnable() {
public void run() { wife.eatWith(s, husband); }
}).start();
}
}
万水千山总是情,麻烦手下别留情。
如若讲得有不妥,文末留言告知我,
如若觉得还可以,收藏点赞要一起。
opLW原创七言律诗,转载请注明出处