在多线程并发中,两个及以上线程互相持有对方所需要的资源又不主动释放,导致程序进入无尽的阻塞这就是“死锁”;
import java.util.concurrent.TimeUnit;
/**
* 写一段必然发生死锁代码
*/
public class MustDeadLock implements Runnable{
// 定义标记位,用于指示不同的线程获取不同的锁
int flag = 1;
// 定义两把锁
static Object lockOne = new Object();
static Object lockTwo = new Object();
@Override
public void run() {
if(flag == 1){
synchronized (lockOne){ // 获取锁“lockOne”
System.out.println("线程一获取到了锁lockOne");
try {
TimeUnit.MILLISECONDS.sleep(500); // 休眠的意义在于等待两个线程都已成功获取了“第一把锁”
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockTwo){ // 获取锁“lockOne”成功后,尝试获取锁“lockTwo”
System.out.println("线程一获取到了两把锁...");
}
}
}
if(flag == 2){
synchronized (lockTwo){ // 获锁“lockTwo”
System.out.println("线程二获取到了锁lockTwo");
try {
TimeUnit.MILLISECONDS.sleep(500); // 休眠的意义在于等待两个线程都已成功获取了“第一把锁”
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockOne){ // 获取锁“lockTwo”成功后,尝试获取锁“lockOne”
System.out.println("线程二获取到了两把锁...");
}
}
}
}
// 测试
public static void main(String[] args) {
MustDeadLock r1 = new MustDeadLock();
MustDeadLock r2 = new MustDeadLock();
r1.flag = 1; // 设置标记位
r2.flag = 2; // // 设置标记位
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
使用前提:已知死锁可能发生在哪个类中;
流程:
1.运行上面的死锁代码,让程序先出现死锁问题;
2.进入程序所使用JDK的bin目录,执行jps获取到其进程号;
3.再执行jstack "pid" 就可以查找到
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.concurrent.TimeUnit;
/**
* 使用ThreadMXBean检测死锁
*/
public class ThreadMXBeanDetection implements Runnable{
// 定义标记位,用于指示不同的线程获取不同的锁
int flag = 1;
// 定义两把锁
static Object lockOne = new Object();
static Object lockTwo = new Object();
@Override
public void run() {
if(flag == 1){
synchronized (lockOne){ // 获取锁“lockOne”
System.out.println("线程一获取到了锁lockOne");
try {
TimeUnit.MILLISECONDS.sleep(500); // 休眠的意义在于等待两个线程都已成功获取了“第一把锁”
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockTwo){ // 获取锁“lockOne”成功后,尝试获取锁“lockTwo”
System.out.println("线程一获取到了两把锁...");
}
}
}
if(flag == 2){
synchronized (lockTwo){ // 获锁“lockTwo”
System.out.println("线程二获取到了锁lockTwo");
try {
TimeUnit.MILLISECONDS.sleep(500); // 休眠的意义在于等待两个线程都已成功获取了“第一把锁”
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockOne){ // 获取锁“lockTwo”成功后,尝试获取锁“lockOne”
System.out.println("线程二获取到了两把锁...");
}
}
}
}
// 测试
public static void main(String[] args) throws InterruptedException {
ThreadMXBeanDetection r1 = new ThreadMXBeanDetection();
ThreadMXBeanDetection r2 = new ThreadMXBeanDetection();
r1.flag = 1; // 设置标记位
r2.flag = 2; // // 设置标记位
Thread t1 = new Thread(r1,"线程一");
Thread t2 = new Thread(r2,"线程二");
t1.start();
t2.start();
TimeUnit.MILLISECONDS.sleep(500);
// 此处开始使用ThreadMXBean对象检测“死锁”
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if(deadlockedThreads != null && deadlockedThreads.length > 0){
for (int i = 0; i < deadlockedThreads.length; i++) {
ThreadInfo threadInfo = threadMXBean.getThreadInfo(deadlockedThreads[i]); // 线程信息
String threadName = threadInfo.getThreadName();
System.out.println("发生“死锁”的线程名字为:" + threadName);
// TODO 当然已经检测到发生“死锁”问题,那你可以发送邮件给开发者让其处理,也可以让其自动重启;
}
}
}
}
说明:如果发生“死锁”的概率极低,那我们就直接忽略它,直到“死锁”发生时再去处理它;
说明:“哲学家就餐例子”换手方案;
说明:允许发生“死锁”,发生死锁时“我”帮你检测出来并恢复;
根据我们自己判断的优先级去终止一些对业务影响较小且造成死锁的线程;
缺点:可能同一个线程一直被抢占造成该线程一直无法获取到锁“饥饿”问题;
ConcurrentHashMap、ConcurrentLinkedQueue、AtomicBoolean等...
使用同步代码块可以自己指定锁对象,锁的范围小于等于同步方法;
如果发现“死锁”或线程安全问题时,我们根据线程名去排查问题时就可以事半功倍;
“银行家算法”,分配锁之前看能不能收回锁假如分配锁后能收回锁,就算发生死锁我们
也可以让线程执行回退两步回收锁,避免死锁;
活锁表示执行任务的线程没有被阻塞,但由于某些条件没有被满足导致重复的去尝试执行任务(失败->尝试->失败),“活锁”与“死锁”的区别在于“死锁”线程一直处于等待状态而“活锁”执行线程在不断的执行;
加入随机因素;加入随机数让线程双方不再让渡资源优先随机使其一方先执行后再释放锁;
活锁案例:活锁——牛郎织女的幸福生活_牛郎织女幸福生活-CSDN博客文章浏览阅读692次,点赞4次,收藏6次。从前,有一对非常恩爱的夫妻,他们都有一颗谦让的心,但家境却不是很好,吃了上顿没下顿,于是,当他们有食物的时候,他们会优先考虑对方,如果对方饿的话,就让给对方吃,等对方吃饱了自己才吃,这种美德本身是好的,但是如果一味的谦让,也会发生一些让人啼笑皆非的事儿…_牛郎织女幸福生活https://blog.csdn.net/qq_36221788/article/details/103078399 “消息队列”就是一个活锁例子,如果消息处理失败就放在队列开头重试,由于服务出现问题导
致该消息一直重试失败;
“饥饿”是指线程执行时无法获取到它所需要的资源比如(CPU),一种情况是“线程”优先级分配不合理导致部分线程一直无法被CPU调度执行;另一种情况是某线程持有某个操作的“锁”但其又处于无限循环而又不释放锁,此时其它等待获取此锁的线程获取不到就出现了饥饿问题;饥饿问题会导致服务器响应变差;
详见本章序号2;
一个方法中获取多把锁时易发生死锁问题;或循环调用获取锁会形成一个闭合的死锁链路;
比如库存的增减,金额的增减;
1.互斥(资源只能同时被一个线程竞争获取)
2.某线程已持有一把锁在不释放已获取到的锁,同时再去不停的获取另一把锁;
3.其它线程不能释放这个线程已获取到的锁;
4.循环等待(多线程中循环等待构成环路才会发生死锁)
详见本章序号3;
详见本章序号4;
详见本章序号5;
详见本章序号1、7、8;
悟空老师Java并发编程思维导图:
https://naotu.baidu.com/file/ec7748c253f4fc9d88ac1cc1e47814f3