问题:你是怎么发现死锁并且是如何预防、如何解决的?
死锁的定义:
这里给出一个我对死锁的概念的理解:
多个线程同时被阻塞,并且他们中的一个或多个都在等待某个被占用资源的释放。
再给出一种百度百科上的解释,比较全面,便于交叉理解:
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
死锁产生的必要条件:
- 互斥 :一个资源每次只能被一个线程占用
- 不可剥夺:进程对正在被其他进程占用的资源,不能强行剥夺
- 请求与保持: 一个进程因请求资源而阻塞时,对已获得的资源保持不放
- 循环等待:若干进程之间形成一种头尾相接的循环等待资源关系。
注:只要一个条件不满足,就不会发生死锁,所以避免死锁,或者是解决死锁问题,只需要破坏其中一个必要条件即可。
死锁问题例子:
A 线程要在持有锁a的前提下尝试获取锁b
B 线程要在持有锁b的前提下尝试获取锁a
A B 在完成获取锁的动作之前,都不会放弃自身持有的锁,死锁条件达成,接下来是代码:
class Solution {
public static void main(String[] args) {
final Object a = new Object();
final Object b = new Object();
Thread threadA =
new Thread(
new Runnable() {
@Override
public void run() {
synchronized (a) {
System.out.println("threadA in a lock");
try {
Thread.sleep(1000);
synchronized (b) {
System.out.println("threadA in b lock");
}
} catch (InterruptedException e) {
// ..
}
}
}
});
Thread threadB =
new Thread(
new Runnable() {
@Override
public void run() {
synchronized (b) {
System.out.println("threadB in a lock");
try {
Thread.sleep(1000);
synchronized (a) {
System.out.println("threadB in b lock");
}
} catch (InterruptedException e) {
// ..
}
}
}
});
threadA.start();
threadB.start();
}
}
运行结果:
threadA in a lock
threadB in a lock
产生死锁,程序明显停滞了,没有剩余的输出。
死锁检测:
这里我们可以使用JDK提供的两种检测工具,进行简易的死锁检测
Jstack 命令:
jstack工具可以用于生成Java虚拟机当前的快照,是虚拟机中每一条线程正在执行的方法堆栈的集合。
方法:
1 我们可以通过 jps获取当前任务的进程号
E:\react\spring>jps
10256
20404 Launcher
8036 Jps
8188 Solution
2 可以确认任务的进程号是8188,然后执行jstack查看当前进程的堆栈信息
Found one Java-level deadlock:
"Thread-1":
waiting to lock monitor 0x17677e84 (object 0x073f0dd0, a java.lang.Object), which is held by "Thread-0"
"Thread-0": waiting to lock monitor 0x176798c4 (object 0x073f0dd8, a java.lang.Object),
which is held by "Thread-1"
译为:
hread-1这个进程,正在等待一个锁的释放,这个monitor的地址是 0x17677e84,它正在被Thread-0这个线程持有
Thread-0这个进程,正在等待一个锁的释放,这个monitor的地址是 0x176798c4,它正在被Thread-1这个线程持有。
可以很明显的看出,确实存在死锁。
JConsole工具:
JConsole是JDK自带的监控工具,在jdk/bin目录下就可以找到,它用来连接正在运行的本地或远程JVM,对运行在Java应用程序的资源消耗和性能进行监控,并且会以图表的形式进行展示。并且本地占用的服务器内存很小,使用起来非常方便。
方法:
1 在命令行中敲入 jconsole 选择进程号 8188 进行连接
监控图表:
2 选择线程选项卡,点击检测死锁
如图锁时,使用JConsole的时候,也会显示类似Jstack的信息,发现死锁的线程。
如何规避死锁:
正如之前提到的,避免死锁,或者说解决死锁问题,就需要破坏死锁发生的必要条件。
1 尽量不要去使用锁的嵌套,以确定的顺序获取锁 ,避免可能产生的循环依赖问题。
修改后的代码:
class Solution {
public static void main(String[] args) {
final Object a = new Object();
final Object b = new Object();
Thread threadA =
new Thread(
new Runnable() {
@Override
public void run() {
synchronized (a) {
System.out.println("threadA in a lock");
}
synchronized (b) {
System.out.println("threadA in b lock");
}
}
});
Thread threadB =
new Thread(
new Runnable() {
@Override
public void run() {
synchronized (b) {
System.out.println("threadB in a lock");
}
synchronized (a) {
System.out.println("threadB in b lock");
}
}
});
threadA.start();
threadB.start();
}
}
执行结果:
threadA in a lock
threadA in b lock
threadB in a lock
threadB in b lock
2 超时放弃:
可以用ReentrantLock中的 tryLock(long time,TimeUnit unit)方法来方式获取锁,该方法会按照固定时长去等待锁,因此线程可以在获取锁超时之后,主动释放之前已经释放的锁。(内部是一个自旋锁,不断的尝试获取锁,超时之后抛出异常)。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
class Solution {
public static void main(String[] args) throws InterruptedException {
ReentrantLock lockA = new ReentrantLock();
ReentrantLock lockB = new ReentrantLock();
new Thread(new Runnable() {
@Override
public void run() {
try {
lockA.lock();
System.out.println("Thread-2 in Alock");
Thread.sleep(100);
lockB.tryLock(1,TimeUnit.SECONDS);
System.out.println("Thread-2 in Block");
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lockB.unlock();
lockA.lock();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
lockB.lock();
System.out.println("Thread-1 in Block");
Thread.sleep(100);
lockA.lock();
System.out.println("Thread-1 in Alock");
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lockA.unlock();
lockB.lock();
}
}
}).start();
}
}
执行结果:
Thread-2 in Alock
Thread-1 in Block
Thread-2 in Block
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151) at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261) at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457) at Solution$1.run(Solution.java:22) at java.lang.Thread.run(Thread.java:748)
虽然Thread-0 抛出异常,但是Thread-1中的语句全部执行,并没有发生死锁,程序不会阻塞住。