所谓死锁: 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
百度百科
我们使用加锁机制来确保线程安全,但如果过度使用加锁,则可能导致锁顺序死锁(Lock-Ordering Deadlock).同样,我们使用线程池和信号量来限制对资源的使用,但这些被限制的行为可能会导致资源死锁(Resource DeadLock).
Java应用程序无法从死锁中恢复过来,因此在程序设计时要排除那些可能导致死锁出现的条件
下面对死锁的一些简单介绍
一,死锁示例
1,锁顺序死锁
- 线程以不同的顺序来获得相同的锁,那么就可能出现死锁
- 若所有线程以固定的顺序来获得锁,那么在程序中就不会出现锁顺序死锁的问题
示例:
public class Test {
public static void main(String[] args) {
LeftRightDeadLock deadLock = new LeftRightDeadLock();
LeftRightThread leftRightThread = new LeftRightThread(deadLock);
RightLeftThread rightLeftThread = new RightLeftThread(deadLock);
leftRightThread.start();
rightLeftThread.start();
}
}
class LeftRightThread extends Thread{
private LeftRightDeadLock deadLock;
public LeftRightThread(LeftRightDeadLock deadLock) {
this.deadLock = deadLock;
}
@Override
public void run() {
while(true){
deadLock.leftRight();
}
}
}
class RightLeftThread extends Thread{
private LeftRightDeadLock deadLock;
public RightLeftThread(LeftRightDeadLock deadLock) {
this.deadLock = deadLock;
}
@Override
public void run() {
while(true){
deadLock.rightLeft();
}
}
}
class LeftRightDeadLock{
private Object leftLock = new Object();
private Object rightLock = new Object();
public void leftRight(){
synchronized (leftLock) {
synchronized (rightLock) {
System.out.println(Thread.currentThread().getName()+" leftRight");
}
}
}
public void rightLeft(){
synchronized (rightLock) {
synchronized (leftLock) {
System.out.println(Thread.currentThread().getName()+" rightLeft");
}
}
}
通过让线程获取锁的顺序一致来避免死锁
class LeftRightDeadLock{
private Object leftLock = new Object();
private Object rightLock = new Object();
public void leftRight(){
synchronized (leftLock) {
synchronized (rightLock) {
System.out.println(Thread.currentThread().getName()+" leftRight");
}
}
}
public void rightLeft(){
synchronized (leftLock) {
synchronized (rightLock) {
System.out.println(Thread.currentThread().getName()+" rightLeft");
}
}
}
2,在协作对象之间发生的死锁
- 如果在持有锁时调用某个外部方法,那么将出现活跃性问题.在这个外部方法中可能会获取其他锁(这可能会产生死锁),或者阻塞时间过长,导致其他线程无法及时获得当前被持有的锁
- 解决办法---开放调用
如果在调用某个方法时不需要持有锁,那么这种调用被称为开放调用(Open Call)
public class Test {
public static void main(String[] args) throws InterruptedException {
First first = null;
Second second = null;
first = new First();
second = new Second();
first.setSecond(second);
second.setFirst(first);
FirstThread firstThread = new FirstThread(first);
SecondThread secondThread = new SecondThread(second);
firstThread.start();
Thread.sleep(1000);
secondThread.start();
}
}
class FirstThread extends Thread{
First first;
FirstThread(First first){
this.first = first;
}
@Override
public void run() {
for(;;){
first.getFirst();
}
}
}
class SecondThread extends Thread{
Second second;
SecondThread(Second second){
this.second = second;
}
@Override
public void run() {
for(;;){
second.getSecond();
}
}
}
class First {
private Second second;
public synchronized String getFirst() {
second.setFirst(this);
System.out.println(Thread.currentThread());
return "first";
}
public synchronized void setSecond(Second second){
this.second = second;
}
}
class Second {
private First first;
public synchronized String getSecond() {
first.setSecond(this);
System.out.println(Thread.currentThread());
return "second";
}
public synchronized void setFirst(First first){
this.first = first;
}
}
解决后的代码
class First {
private Second second;
public synchronized String getFirst() {
second.setFirst(this);
synchronized (this) {
System.out.println(Thread.currentThread());
}
return "first";
}
public synchronized void setSecond(Second second){
this.second = second;
}
}
class Second {
private First first;
public String getSecond() {
first.setSecond(this);
synchronized (this) {
System.out.println(Thread.currentThread());
}
return "second";
}
public synchronized void setFirst(First first){
this.first = first;
}
}
3,资源死锁
当多个线程相互持有彼此正在等待的锁而又不释放自己已持有的锁时会发生死锁,当它们在相同的资源集合上等待时,也会发生死锁.
二,死锁的避免和诊断
1,死锁的避免
使用Lock类定时的tryLock功能来代替内置锁机制,可以检测死锁和从死锁中回复过来.
当使用内置锁时,只有没有获得锁,就会永远等待下去,而显示锁则可以指定一个超时时限,在等待超过该时间后tryLock会返回一个失败信息.
2,通过线程转储信息来分析死锁
JVM可以通过线程转储(Thread Dump)来帮助识别死锁的发生.
线程转储包括各个运行中的线程的栈追踪信息,这类似于发生异常时的栈追踪信息.线程转储还包括加锁信息,例如每个线程持有了那些锁,在那些栈帧中获得这些锁,以及被阻塞的线程正在等待获取哪一个锁.
以第一个死锁示例(锁顺序死锁)
输入命令: jstack -l 16601
其中16601是进程号
下面是部分的线程转储信息
........
........
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x00007fa04381beb8 (object 0x00000007aaadd8b0, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x00007fa04381bf68 (object 0x00000007aaadd8c0, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at LeftRightDeadLock.rightLeft(Test.java:54)
- waiting to lock <0x00000007aaadd8b0> (a java.lang.Object)
- locked <0x00000007aaadd8c0> (a java.lang.Object)
at RightLeftThread.run(Test.java:35)
"Thread-0":
at LeftRightDeadLock.leftRight(Test.java:46)
- waiting to lock <0x00000007aaadd8c0> (a java.lang.Object)
- locked <0x00000007aaadd8b0> (a java.lang.Object)
at LeftRightThread.run(Test.java:21)
Found 1 deadlock.
参考:
<<java编发编程实战>>
网友评论