java并发-实例讲解死锁的产生,发现,解决。活锁,饥饿的概念
前言
死锁似乎是java面试或者笔试中必问的一个东西,还是需要搞清楚的,本文从什么是死锁,为什么死锁,如何解决死锁3个角度来描述
什么是死锁
当有两个或更多的线程在等待对方释放锁并无限期地卡住时,这种情况就称为死锁。
比如:
线程A,持有资源1,它只有获得资源2才能完成任务;
线程B,持有资源2,它只有获得资源1才能完成任务。
出现死锁原因,它们都想着获得对方手中的资源,但是却不肯放弃自己手上的资源
死锁的四种条件
1、互斥性:一个资源每次只能被一个进程使用。
2、请求和保持:一个进程请求新的资源的时候,不会释放已有的资源。
3、不剥夺条件:进程已有的资源在完成任务之前不能被剥夺。只能自己释放
4、循环等待条件:进程之间形成首尾相接的循环等待资源的关系。
死锁的小例子
package thread;
public class Test4 {
public Object lock1= new Object();
public Object lock2 = new Object();
public static void main(String[] args) {
Test4 test = new Test4();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (test.lock1) {
System.out.println(Thread.currentThread().getName()+"获得了锁1");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (test.lock2) {
System.out.println(Thread.currentThread().getName()+"获得了锁2");
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (test.lock2) {
System.out.println(Thread.currentThread().getName()+"获得了锁2");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (test.lock1) {
System.out.println(Thread.currentThread().getName()+"获得了锁1");
}
}
}
}).start();
}
}
如何查看死锁?
常用的两种方法:
1、jstack
// jps查看进程号
aaa@qq.com_lyy:~$ jps
20419 Test4
20792 Jps
8606 org.eclipse.equinox.launcher_1.4.0.v20161219-1356.jar
// jstack + pid
Java stack information for the threads listed above:
===================================================
"Thread-1":
at thread.Test4$2.run(Test4.java:40)
- waiting to lock <0x000000008c303768> (a java.lang.Object)
- locked <0x000000008c303778> (a java.lang.Object)
at java.lang.Thread.run(java.base@9.0.4/Thread.java:844)
"Thread-0":
at thread.Test4$1.run(Test4.java:22)
- waiting to lock <0x000000008c303778> (a java.lang.Object)
- locked <0x000000008c303768> (a java.lang.Object)
at java.lang.Thread.run(java.base@9.0.4/Thread.java:844)
Found 1 deadlock.
2、jconsole
死锁的解决
1、注意加锁的顺序
package thread;
public class Test4 {
public Object lock1= new Object();
public Object lock2 = new Object();
public static void main(String[] args) {
Test4 test = new Test4();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (test.lock1) {
System.out.println(Thread.currentThread().getName()+"获得了锁1");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (test.lock2) {
System.out.println(Thread.currentThread().getName()+"获得了锁2");
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (test.lock1) {
System.out.println(Thread.currentThread().getName()+"获得了锁1");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (test.lock2) {
System.out.println(Thread.currentThread().getName()+"获得了锁2");
}
}
}
}).start();
}
}
2、避免无限等待
当使用synchronized关键词提供的内置锁时,只要线程没有获得锁,那么就会永远等待下去,然而Lock接口提供了boolean tryLock(long time, TimeUnit unit) 方法,该方法可以按照固定时长等待锁,因此线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁。
package thread;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class Test4 {
ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();
public static void main(String[] args) {
Test4 test = new Test4();
new Thread(new Runnable() {
@Override
public void run() {
test.lock1.lock();
if (test.lock1.isHeldByCurrentThread()) {
System.out.println(Thread.currentThread().getName() + "获得了锁1");
}
try {
Thread.sleep(300);
test.lock2.tryLock(200, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (test.lock2.isHeldByCurrentThread()) {
System.out.println(Thread.currentThread().getName() + "获得了锁2");
test.lock2.unlock();
}
}
test.lock1.unlock();
}
}, "A").start();
new Thread(new Runnable() {
@Override
public void run() {
test.lock2.lock();
if (test.lock2.isHeldByCurrentThread()) {
System.out.println(Thread.currentThread().getName() + "获得了锁2");
}
try {
Thread.sleep(300);
test.lock1.tryLock(300, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (test.lock1.isHeldByCurrentThread()) {
System.out.println(Thread.currentThread().getName() + "获得了锁1");
test.lock1.unlock();
}
}
test.lock2.unlock();
}
}, "B").start();
}
}
----output----
A获得了锁1
B获得了锁2
B获得了锁1
注意,这里finally里面的部分不能少,不然释放了锁之后,用unlock解锁就会报错,你没有这个锁 却去解锁,自然会报错。
什么是活锁
T1能使用资源A,但是它比较礼貌,让给了T2,T2此时能使用资源A,但是它也很绅士,选择让给T1,于是就这么让来让去,循环下去
什么是饥饿
非公平锁的抢占机制就会可能导致饥饿,当T1线程占用了资源A,T2,T3,T4。。。都在等待锁的释放,当锁释放的时候,T3抢到了资源,之后又释放锁,T4抢到了资源。。。T2 总是抢不到。这就是饿死
总结
目前笔者能力范围之内 能找到的解锁方法就是这两种了,因该还有别的比较好的方法,但是我没能发现。作为一个基础了解一下还是挺好的