有段时间没写博客了,最近在基于java学多线程,同时在学校上《操作系统》这门课,可以说生活中充满了”锁”,我对锁的认识,也不单单是宿舍门上的那一把金属锁了。
锁有很多种,偏向锁,轻量级锁,重量级锁,自旋锁,重入锁…..等等,这些锁之后有空我再总结一下,之后再写。
这里我就谈谈“死锁”这个多线程中的现象。
首先有必要了解一下死锁产生的4个必要条件:
①互斥:某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
②占有且等待:一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
③不可抢占:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
④循环等待:存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。
当以上四个条件均满足,必然会造成死锁,发生死锁的进程无法进行下去,它们所持有的资源也无法释放。CPU吞吐量会下降,所以会浪费系统资源,所以说死锁是我们不想看到的情况。
作个不太正经的比喻,死锁“像极了爱情”,为什么这么说呢?
如果把女生比作资源,男生比作进程的话。
互斥:男生(进程)已经和女生(资源)确立了恋爱关系,那么其他的男生(进程)就不能再插足(访问),直到他们分手(该进程访问结束)。
占有且等待:某渣男(进程)已经和一个女生(资源)确立了恋爱关系,但因为他是渣男,所以他一个女友是不够的,以至于他一直在等一个女生恢复单身后(别的进程释放资源),再脚踏两只船(获取这个资源)。
不可抢占:朋友妻不可欺。
循环等待:一个男生(进程)和一个女生(资源)确立了恋爱关系后,这个男生不会和其他的异性朋友完全断了联系,异性朋友之一(另一个资源)可能已经和别的男生(另一个进程)确立了关系,但是“缘,妙不可言”,你懂得。
其实说上面这些感性的话,是想帮助大家更好理解一点,没有别的意思。但是因人而异吧,我算是比较感性的那种人哈哈。
不再扯远了,直接上一个死锁的例子吧。
public class Test14 {
public static String obj1 = "work1";
public static String obj2 = "work2";
public static void main(String[] args) {
A a = new A();
B b = new B();
a.start();
b.start();
}
}
class A extends Thread {
@Override
public void run() {
System.out.println(this.currentThread().getName() + " is running");
while (true) {
synchronized (Test14.obj1) {
System.out.println("LOCK1 locked work1");
try {
Thread.sleep(3000);//给一定的时间让B争夺obj2
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Test14.obj2) {//产生死锁
System.out.println("LOCK1 locked work2");
}
}
}
}
}
class B extends Thread {
@Override
public void run() {
System.out.println(this.currentThread().getName() + " is running");
while (true) {
synchronized (Test14.obj2) {
System.out.println("LOCK2 locked work2");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Test14.obj1) {
System.out.println("LOCK2 locked work1");
}
}
}
}
}
执行结果:
上述代码中,A的LOCK1一直锁着work1不放,B的LOCK2也一直锁着work2不放,所以A得不到work2,B也得不到work1,就出现了令人尴尬的死锁现象。
那怎么办?其实解决死锁的经典算法有银行家算法,哲学家就餐算法……这里我就先不赘述,只针对这个Demo寻找解决方法。
其实大家不难发现,这个Demo中只用了synchronized的锁,其实可以用ReentranLock 中的 tryLock 避免死锁(因为有try块我就可以写finally,不管不顾先用unlock()解锁,让A不得不释放work1,B同样,这样就可以解决死锁问题了)。
上一下代码:
public class Test15 { public static String obj1 = "work1"; public static String obj2 = "work2"; public static ReentrantLock LockA = new ReentrantLock(); public static ReentrantLock LockB = new ReentrantLock(); public static void main(String[] args) { A a = new A(); B b = new B(); a.start(); b.start(); } static class A extends Thread { @Override public void run() { System.out.println(this.currentThread().getName() + " is running"); while (true) { Test15.LockA.tryLock(); System.out.println("LOCK1 locked work1"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } finally { Test15.LockA.unlock(); } Test15.LockB.tryLock(); System.out.println("LOCK1 locked work2"); } } } static class B extends Thread { @Override public void run() { System.out.println(this.currentThread().getName() + " is running"); while (true) { Test15.LockB.tryLock(); System.out.println("LOCK2 locked work1"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } finally { Test15.LockB.unlock(); ; } Test15.LockB.tryLock(); System.out.println("LOCK2 locked work2"); } } } }
执行结果:
LOCK1可以获取work2,LOCK2可以获取work1,可以见得死锁问题解决了。
且学且思且凝练