什么是死锁
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
我们的线程是交错执行的,那么就很有可能出现以下的情况:
class Lipstick {//口红类
}
class Mirror {//镜子类
}
class Makeup extends Thread {//化妆类继承了Thread类
boolean flag;
String girl;
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
@Override
public void run() {
// TODO Auto-generated method stub
doMakeup();
}
void doMakeup() {
if (flag == 0) {
synchronized (lipstick) {//需要得到口红的“锁”;
System.out.println(girl + "拿着口红!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (mirror) {//需要得到镜子的“锁”;
System.out.println(girl + "拿着镜子!");
}
}
} else {
synchronized (mirror) {
System.out.println(girl + "拿着镜子!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lipstick) {
System.out.println(girl + "拿着口红!");
}
}
}
}
public class TestDeadLock {
public static void main(String[] args) {
Makeup m1 = new Makeup();//丽丽的化妆线程;
m1.girl = "丽丽";
m1.flag = true;
Makeup m2 = new Makeup();//玛丽的化妆线程;
m2.girl = "玛丽";
m2.flag = false;
m1.start();
m2.start();
}
}
执行结果:
丽丽拿着口红
玛丽拿着镜子
- 线程丽丽调用方法,得到口红锁
- 同时线程玛丽调用方法,得到镜子锁
- 线程丽丽和线程玛丽都继续执行,此时线程丽丽需要镜子锁才能继续往下执行。
- 此时线程玛丽需要口红锁才能继续往下执行。
- 但是:线程丽丽的口红锁并没有释放,线程玛丽的镜子锁也没有释放。
- 所以他们都只能等待,而这种等待是无期限的–>永久等待–>死锁
造成死锁的原因可以概括成三句话:
- 当前线程拥有其他线程需要的资源
- 当前线程等待其他线程已拥有的资源
- 都不放弃自己拥有的资源
死锁的解决方案
加锁顺序:因为按照了不同顺序加锁,死锁便容易产生。所以如果在确保线程都是按照相同的顺序获得所,那就不存在此现象。
class Lipstick {//口红类
}
class Mirror {//镜子类
}
class Makeup extends Thread {//化妆类继承了Thread类
boolean flag;
String girl;
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
@Override
public void run() {
// TODO Auto-generated method stub
doMakeup();
}
void doMakeup() {
if (flag == 0) {
synchronized (lipstick) {
System.out.println(girl + "拿着口红!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (mirror) {
System.out.println(girl + "拿着镜子!");
}
} else {
synchronized (mirror) {
System.out.println(girl + "拿着镜子!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (lipstick) {
System.out.println(girl + "拿着口红!");
}
}
}
public class TestDeadLock {
public static void main(String[] args) {
Makeup m1 = new Makeup();//丽丽的化妆线程;
m1.girl = "丽丽";
m1.flag = true;
Makeup m2 = new Makeup();//玛丽的化妆线程;
m2.girl = "玛丽";
m2.flag = false;
m1.start();
m2.start();
}
}
执行结果:
丽丽拿着口红
玛丽拿着镜子
玛丽拿着口红
丽丽拿着镜子
死锁检测
- Jstack命令
jstack用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待,线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。
- JConsole工具
Jconsole是JDK自带的监控工具,在JDK/bin目录下可以找到。它用于连接正在运行的本地或者远程的JVM,对运行在Java应用程序的资源消耗和性能进行监控,并画出大量的图表,提供强大的可视化界面。而且本身占用的服务器内存很小,甚至可以说几乎不消耗。