我们知道线程长时间停顿的主要原因有:等待外部资源(数据库连接、网络资源、设备资源等)、死循环、锁等待(死锁和活锁)。
现在写一个线程死锁的小Demo,就我自己的理解就是:线程t1先锁定一个对象A,再锁定对象B。而线程t2先锁定对象B,再锁定对象A。当线程t1先执行时,并没有释放锁对象B,但是线程t2要锁定对象B(线程t1并没有释放锁),同理,要是线程t2先执行,并没有释放锁对象A,但是线程t1要锁定对象A(线程t2并没有释放锁),此时就形成锁的相互争夺,相互不释放,造成线程死锁问题。
下面来看一个小Demo
public class DeadLockTest {
static class DeadLock implements Runnable{
int a,b;
public DeadLock(int a,int b) {
this.a=a;
this.b=b;
}
public void run() {
synchronized (Integer.valueOf(a)) {
synchronized (Integer.valueOf(b)) {
sum(a, b);
}
}
}
int sum(int a,int b){
return a+b;
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(new DeadLock(1, 2)).start();
new Thread(new DeadLock(2, 1)).start();
}
}
}
这段代码开了200个线程去计算1+2的值,其实不用for循环也会造成死锁,只是概率比较小,要是很多次才能成功(因为循环时一个线程的执行时间比较短,线程的锁已经释放,所以要试很多次才能看到死锁的现象,要是想一次看到死锁的效果,可以让线程等待,下面会讲到)。造成死锁的原因是Integer.valueOf()方法基于减少对象创建次数和节省内存考虑,[-128,127]之间的数字已经被缓存,当valueOf()输入的数字在这个范围内,则直接返回缓存的对象。
也就是说,代码中跑了200次Integer.valueOf()方法,只返回了两个不同的对象。加入在某个线程块的sychronized块之间发生了一次线程切换,那就会出现线程t1线程t2拥有的Integer.valueOf(1),线程t2线程t1拥有的Integer.valueOf(2),就会出现跑不下去的情况。
我们用jConsole工具来检测线程的死锁,会得到有关死锁的相关信息。
由图中可知线程3 4 199造成死锁,其中线程4的锁在线程3中锁定,线程4和199的锁在3中锁定。
在跑这个程序的时候会出现,跑多次都没有造成死锁现象,解决此问题要在线程睡眠(下面会说到),所以要多跑几次,才能看到死锁的现象。
还有一点奇怪的是每次跑线程199都会出现死锁。不知道为什么固定是线程199
为了解决上面跑了几次没有出现死锁的现象,另外写了一个小Demo
public class DeadLockTest1 {
private static String A="A";
private static String B="B";
private static void deadLock(){
Thread t1=new Thread(new Runnable() {
public void run() {
synchronized (A) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (B) {
System.out.println("我是线程t1");
}
}
}
},"t1");
Thread t2=new Thread(new Runnable() {
public void run() {
synchronized (B) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (A) {
System.out.println("我是线程t2");
}
}
}
},"t2");
t1.start();
t2.start();
}
public static void main(String[] args) {
deadLock();
}
}
在JConsole中检测死锁,信息如下:
名称: t1
状态: java.lang.String@24aa663f上的BLOCKED, 拥有者: t2
总阻止数: 1, 总等待数: 1
名称: t2
状态: java.lang.String@1179bd59上的BLOCKED, 拥有者: t1
总阻止数: 1, 总等待数: 1
现在我们介绍避免死锁的几种常见方法
1、避免一个线程同时获取多个锁
2、避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
3、尝试使用定时锁,使用lock.tryLock(timeout)来替代内部锁机制。
4、对于数据库锁,加锁和解锁必须在同一个数据库连接里,否则会出现解锁失败的情况