什么是死锁
死锁是指多个进程因竞争资源而引起的一种僵局,如果没有外力作用,所有进程都不会向前推进。
举例:假如有线程Thread1和Thread2,两个都要访问共享资源AB,Thread1和Thread2一个先访问A再访问B,另一个先访问B再访问A。但在他们线程未执行完时,都不会释放AB资源,那么就形成了一种僵局,Thread1在等待Thead2施放B资源,Thread2在等待Thread1释放A资源,两个进程就形成了一种僵局,下面以Java代码实现为例说明:
public class DeadThread {
private Thread thread1;
public DeadThread(Object object1,Object object2){
thread1 = new Thread(() -> {
synchronized (object1){
System.out.println("获取到o1"+Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object2){
System.out.println("获取到o2"+Thread.currentThread().getName());
}
}
});
thread1.start();
}
public static void main(String[] args) {
Object object1=new Object();
Object object2=new Object();
DeadThread deadThread=new DeadThread(object1,object2);
DeadThread deadThread1=new DeadThread(object2,object1);
}
}
其中Object1和Object2就是上述的资源AB。
死锁产生的必要条件
- 互斥条件:资源互斥访问。一个资源每次只能被一个进程使用,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
- 请求保持:进程已经拥有了资源,又请求资源。进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
- 不可剥夺:进程拥有的资源不会被剥夺,只能自己主动释放。进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
- 循环等待: 进程之间形成了一个循环等待资源的环。若干进程间形成首尾相接循环等待资源的关系。
死锁产生的原因
死锁产生的原因
进程推进顺序非法。
进程竞争资源。
Java中怎么监测死锁
还是以上述程序为例:可以看到这两个线程都处于blocked状态
用idea自带的debug功能
或者用jconsole工具来查看(未使用过)
操作系统中如何避免或打破死锁
破坏死锁的必要条件,互斥条件是不可变的,只能想办法打破后三个条件。
比如破坏请求保持条件,进程必须一次性申请所需的全部资源。还有著名的银行家算法。等等,这里不作详细介绍
Java中如何避免或打破死锁
根据Java核心技术卷I中的说法,**在Java语言中没有任何东西可以避免或打破这种死锁现象,必须仔细设计程序,以确保不会出现死锁。**言下之意即,程序员在设计线程时,必须想办法尽量避免死锁现象。
后来,查阅资料发现jdk1.5后新增了一个类可用于侦察死锁,见下文Reentranlock处:
程序输出
在这里插入代码片获取到锁2135218919Thread-1
获取到锁131519948Thread-0
---[J@6d311334
tsun.misc.Unsafe.park(Native Method)
tjava.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
tjava.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
tjava.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
tjava.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
tjava.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
tjava.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
tDeadThread1.lambda$new$0(DeadThread1.java:27)
tDeadThread1$$Lambda$1/1096979270.run(Unknown Source)
tjava.lang.Thread.run(Thread.java:748)
tsun.misc.Unsafe.park(Native Method)
tjava.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
tjava.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
tjava.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
tjava.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
tjava.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
tjava.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
tDeadThread1.lambda$new$0(DeadThread1.java:27)
tDeadThread1$$Lambda$1/1096979270.run(Unknown Source)
tjava.lang.Thread.run(Thread.java:748)
可以看到可以快速定位到死锁发生的代码处
①避免锁嵌套
尽量不要使用锁嵌套
如果已经持有一个资源,请避免锁定另一个资源。
这是打破了请求保持条件
②合理的进程推进顺序和合理的加锁顺序
③尽量只锁定必要资源,不要对方法加锁,对方法块加锁
④避免无限期等待
如下列代码
public class DeadJoinExample {
private class A extends Thread{
private B b;
public void setB(B b) {
this.b = b;
}
A(){}
A(B b){
this.b=b;
}
@Override
public void run() {
try {
b.join(1000);
//设置超时时间,避免无线等待
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A");
}
}
private class B extends Thread {
private A a;
B(A a) {
this.a = a;
}
@Override
public void run() {
try {
a.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B");
}
}
public void test() {
A a = new A();
B b = new B(a);
a.setB(b);
a.start();
b.start();
}
public static void main(String[] args) throws InterruptedException {
DeadJoinExample example = new DeadJoinExample();
example.test();
}
}
⑤ReentrantLock提供了lock()、tryLock()、tryLock(long timeout, TimeUnit unit)、lock.lockInterruptibly()
-
lock()
当锁不可用,那么当前线程被阻塞,休眠一直到该锁可以获取, -
trylock()
当获取锁时,如果其他线程持有该锁,无可用锁资 源,直接返回false,这时候线程不用阻塞等待,可以先去做其他事情;
-
tryLock(long timeout, TimeUnit unit)
当获取锁时,在超时等待时间之内,被中断了,那么抛出InterruptedException,不再继续等待
-
lockInterruptibly()
当获取锁时,锁资源不可用,那么该线程开始阻塞休眠等待,但是等待过程中如果有中断事件,那么会停止等待,立即返回.(也就是可以响应中断)
使用ReentrantLock也可以有效避免死锁
示例Demo
public class DeadThread1 {
private Thread thread1;
private ReentrantLock reentrantLock;
private ReentrantLock reentrantLock1;
public DeadThread1(ReentrantLock reentrantLock,ReentrantLock reentrantLock1){
this.reentrantLock=reentrantLock;
this.reentrantLock1=reentrantLock1;
thread1 = new Thread(() -> {
reentrantLock.lock();
System.out.println("获取到锁1"+Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
reentrantLock1.lock();
System.out.println("获取到锁2"+Thread.currentThread().getName());
reentrantLock1.unlock();
reentrantLock.unlock();
});
thread1.start();
}
public static void main(String[] args) {
ReentrantLock reentrantLock=new ReentrantLock();
ReentrantLock reentrantLock1=new ReentrantLock();
DeadThread1 deadThread=new DeadThread1(reentrantLock,reentrantLock1);
DeadThread1 deadThread1=new DeadThread1(reentrantLock1,reentrantLock);
}
}
以上是使用正常锁方法,lock(),线程阻塞
修改一下改成tryLock()
import java.util.concurrent.locks.ReentrantLock;
public class DeadThread1 {
private Thread thread1;
private ReentrantLock reentrantLock;
private ReentrantLock reentrantLock1;
public DeadThread1(ReentrantLock reentrantLock,ReentrantLock reentrantLock1){
this.reentrantLock=reentrantLock;
this.reentrantLock1=reentrantLock1;
thread1 = new Thread(() -> {
reentrantLock.lock();
System.out.println("获取到锁"+reentrantLock.hashCode()+Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
reentrantLock1.tryLock();
//查看r锁是否是当前线程拥有
System.out.println("获取到锁"+reentrantLock1.hashCode()+"了吗?"+reentrantLock1.isHeldByCurrentThread()+" "+Thread.currentThread().getName());
reentrantLock.unlock();
});
thread1.start();
}
public static void main(String[] args) {
ReentrantLock reentrantLock=new ReentrantLock();
ReentrantLock reentrantLock1=new ReentrantLock();
DeadThread1 deadThread=new DeadThread1(reentrantLock,reentrantLock1);
DeadThread1 deadThread1=new DeadThread1(reentrantLock1,reentrantLock);
}
}
lockInterruptibly方法中断死锁
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
public class DeadThread1 {
private Thread thread1;
private ReentrantLock reentrantLock;
private ReentrantLock reentrantLock1;
public DeadThread1(ReentrantLock reentrantLock,ReentrantLock reentrantLock1){
this.reentrantLock=reentrantLock;
this.reentrantLock1=reentrantLock1;
thread1 = new Thread(() -> {
reentrantLock.lock();
System.out.println("获取到锁"+reentrantLock.hashCode()+Thread.currentThread().getName());
try {
//可中断得锁
Thread.sleep(1000);
reentrantLock1.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
}
//查看r锁是否是当前线程拥有
System.out.println("获取到锁"+reentrantLock1.hashCode()+"了吗?"+reentrantLock1.isHeldByCurrentThread()+" "+Thread.currentThread().getName());
reentrantLock1.unlock();
reentrantLock.unlock();
});
thread1.start();
}
public static void main(String[] args) throws InterruptedException {
ReentrantLock reentrantLock=new ReentrantLock();
ReentrantLock reentrantLock1=new ReentrantLock();
DeadThread1 deadThread=new DeadThread1(reentrantLock,reentrantLock1);
DeadThread1 deadThread1=new DeadThread1(reentrantLock1,reentrantLock);
//主线程睡眠是为了让死锁先发生
Thread.sleep(4000);
System.out.println("主线程睡眠结束");
ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
//查找发生死锁的线程,返回线程id的数组
long[] deadLockThreadIds = mbean.findDeadlockedThreads();
System.out.println("---" + deadLockThreadIds);
if (deadLockThreadIds != null) {
//获取发生死锁的线程信息
ThreadInfo[] threadInfos = mbean.getThreadInfo(deadLockThreadIds);
//获取JVM中所有的线程信息
Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
for (Map.Entry<Thread, StackTraceElement[]> entry : map.entrySet()) {
for (int i = 0; i < threadInfos.length; i++) {
Thread t = entry.getKey();
if (t.getId() == threadInfos[i].getThreadId()) {
//中断发生死锁的线程
t.interrupt();//线程会相应中断
//打印堆栈信息
/* for (StackTraceElement ste : entry.getValue()) {
System.err.println("t" + ste.toString().trim());
}*/
}
}
}
}
}
}
程序输出:
获取到锁2135218919Thread-1
获取到锁131519948Thread-0
主线程睡眠结束
---[J@6d311334
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at DeadThread1.lambda$new$0(DeadThread1.java:26)
at java.lang.Thread.run(Thread.java:748)
获取到锁2135218919了吗?false Thread-0
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at DeadThread1.lambda$new$0(DeadThread1.java:26)
at java.lang.Thread.run(Thread.java:748)
获取到锁131519948了吗?false Thread-1
Exception in thread "Thread-0" Exception in thread "Thread-1" 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 DeadThread1.lambda$new$0(DeadThread1.java:33)
at java.lang.Thread.run(Thread.java:748)
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 DeadThread1.lambda$new$0(DeadThread1.java:33)
at java.lang.Thread.run(Thread.java:748)
线程被中断不一定意味着线程停止,如何响应中断,取决于线程本身,与其自身写法也有关。Java核心技术卷I 章节14.2中有提及。
部分参考自:https://www.cnblogs.com/liukaifeng/p/10052646.html
https://blog.csdn.net/michaelgo/article/details/81481068
https://www.jianshu.com/p/120872f406f3?from=singlemessage