什么是死锁?
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形.某一个同步块同时拥有“两个以上对象的锁”时,就有可能发生“死锁”的现象.
例子:
package com.company;
//镜子mirror
//口红lipstick
class LipsTick {
}
class Mirror {
}
class makeUp extends Thread {
static LipsTick lipsTick = new LipsTick();//static保证只有一份共享资源,被该类所有的实例共享
static Mirror mirror = new Mirror(); //内存地址是不变的,就算重新建立该类的对象,但是该对象的地址不变
int id;
String name;
makeUp(int id, String name) {
this.id = id;
this.name = name;
}
public void makeUp() throws InterruptedException {
if (this.id == 0) {
synchronized (mirror) { //获得了镜子,独占该镜子资源
System.out.println(this.name + "获得了镜子");
Thread.sleep(1000); //阻塞该线程,让另一线程进入,不会释放锁
synchronized (lipsTick) { //还想要口红
System.out.println(this.name + "获得了口红");
}
}
} else {
synchronized (lipsTick) { //进入第二条线程,获得口红,独占该口红资源
System.out.println(this.name+ "获得了口红");
Thread.sleep(1000); //阻塞该线程,让另一线程进入
synchronized (mirror) {
System.out.println(this.name + "获得了镜子");
}
}
}
}
public void run(){
try {
makeUp();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
makeUp m1 = new makeUp(0, "灰姑娘");
makeUp m2 = new makeUp(1, "白雪公主");
m1.start();
m2.start();
}
}
结果如下:
分析:
代码中含有两个线程,分别是“灰姑娘”和“白雪公主”,在测试类中定义了两种共享资源lipsTick和mirror,static(对象内存地址不变)保证两种资源只有一份.
synchronized (mirror) 让“灰姑娘"独占mirror这个资源,之后利用sleep对该线程进行阻塞,之后更换了线程“白雪公主”,同样的,"白雪公主"利用synchronized (lipsTick)独占了资源lipsTick,再次利用sleep阻塞目前线程,之后cpu调度再次进入了"灰姑娘"的线程,线程继续执行嵌套的 synchronized (lipsTick)语句,“灰姑娘”想要获得lipsTick资源,但是该资源已经被“白雪公主”所在线程的资源上锁独占,这就形成了“灰姑娘”独占mirror资源,“白雪公主”独占lipsTick资源,而双方又想要获得对方的资源,从而程序一直处于等待状态,即所谓的死锁.
死锁图示:
如何预防死锁?
- 产生死锁的四个条件
- 互斥条件:一个资源每次只能被一个进程使用(不可改变).
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放.
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺.
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系.
上面列出了死锁的四个必要条件,我们只要想办法破其中一个或多个条件就可以避免死锁.
修改之后的代码:
package com.company;
//镜子mirror
//口红lipstick
class LipsTick {
}
class Mirror {
}
class makeUp extends Thread {
static LipsTick lipsTick = new LipsTick();//static保证只有一份共享资源,被该类所有的实例共享
static Mirror mirror = new Mirror(); //内存地址是不变的,就算重新建立该类的对象,但是该对象的地址不变
int id;
String name;
makeUp(int id, String name) {
this.id = id;
this.name = name;
}
public void makeUp() throws InterruptedException {
if (this.id == 0) {
synchronized (mirror) { //获得了镜子,独占该镜子资源
System.out.println(this.name + "获得了镜子");
Thread.sleep(1000); //阻塞该线程,让另一线程进入,不会释放锁
}
synchronized (lipsTick) { //还想要口红
System.out.println(this.name + "获得了口红");
}
} else {
synchronized (lipsTick) { //进入第二条线程,获得口红,独占该口红资源
System.out.println(this.name + "获得了口红");
Thread.sleep(1000); //阻塞该线程,让另一线程进入
}
synchronized (mirror) {
System.out.println(this.name + "获得了镜子");
}
}
}
public void run() {
try {
makeUp();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
makeUp m1 = new makeUp(0, "灰姑娘");
makeUp m2 = new makeUp(1, "白雪公主");
m1.start();
m2.start();
}
}
运行结果:、
可以看出,程序正常执行了.在没有修改之前,线程1没有释放锁,因为同步块中嵌套了一个同步块,而外层的同步块没有结束,锁1无法被释放.修改之后,将各个同步块分离,不形成嵌套,所以预防了死锁形成的条件2,所以死锁没有形成.