Day284&285&286&287.死锁 -Juc
Posted 阿昌喜欢吃黄桃
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Day284&285&286&287.死锁 -Juc相关的知识,希望对你有一定的参考价值。
死锁
一、死锁的定义&危害
1、死锁是什么
-
发生在
并发
中 -
互不想让
:当两个(或更多)线程(或进程)相互持有对方所需要的资源,又不主动释放,导致所有人都无法进行千金,导致程序陷入无尽的阻塞,这就是死锁。
- 多个线程造成死锁的情况
- 如果多个线程之间的依赖关系是环形,存在环路的锁的依赖关系,那么也可能会发送死锁
2、死锁的影响
死锁的影响在不同的系统中是不一样的,这取决于系统对死锁的处理能力
-
数据库
中: 检测并放弃事务 -
JVM
中: 无法自动处理
3、死锁的危害
二、发生死锁的例子
1、最简单的情况
- 代码
/******
@author 阿昌
@create 2021-05-31 21:16
*******
* 必定发生死锁的情况
*/
public class MustDeadLock implements Runnable {
int flag = 1;//标记位
static Object lock1 = new Object();
static Object lock2 = new Object();
public static void main(String[] args) {
MustDeadLock r1 = new MustDeadLock();
MustDeadLock r2 = new MustDeadLock();
r1.flag=1;
r2.flag=0;
Thread thread1 = new Thread(r1);
Thread thread2 = new Thread(r2);
thread1.start();
thread2.start();
}
@Override
public void run() {
System.out.println("flag= " + flag);
if (flag == 1) {
synchronized (lock1){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2){
System.out.println(flag);
}
}
}
if (flag == 0) {
synchronized (lock2){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1){
System.out.println(flag);
}
}
}
}
}
- 分析
当thread1拿到lock1后休眠500ms,在此期间,thread2拿到了lock2后休眠500ms;
这次thread1视图想去获取lock2,但是lock2在thread2,没有被thread2释放,因此thread1会陷入阻塞,而thread会去那lock1,但是lock在thread1手上,没有释放,所以他们thread1和thread2都进入了阻塞,相互等待。所以进入了死锁
2、实际生产的转账案例
- 正常成功的情况:↓↓↓
public class TransferMoney implements Runnable {
Integer flag = 1;
static Account a = new Account(500);
static Account b = new Account(500);
//主函数
public static void main(String[] args) throws InterruptedException {
TransferMoney t1 = new TransferMoney();
TransferMoney t2 = new TransferMoney();
t1.flag = 1;
t1.flag = 0;
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t2);
thread1.start();
thread2.start();
thread1.join();
thread1.join();
System.out.println("a的余额为:"+a.balance);
System.out.println("a的余额为:"+b.balance);
}
@Override
public void run() {
if (flag == 1) {
transferMoney(a, b, 200);
}
if (flag == 0) {
transferMoney(b, a, 200);
}
}
//转账
private void transferMoney(Account from, Account to, int amount) {
synchronized (from) {
synchronized (to) {
if (from.balance - amount < 0) {
System.out.println("余额不足");
}
from.balance -= amount;
to.balance += amount;
System.out.println("转账成功");
}
}
}
static class Account {
//余额
int balance;
public Account(int balance) {
this.balance = balance;
}
}
}
- 出现死锁的情况:↓↓↓
//转账
private void transferMoney(Account from, Account to, int amount) {
synchronized (from) {
//加入线程睡眠500ms
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (to) {
if (from.balance - amount < 0) {
System.out.println("余额不足");
}
from.balance -= amount;
to.balance += amount;
System.out.println("转账成功");
}
}
}
陷入死锁…
- 分析
当线程1执行transferMoney()方法的时候,他拿到from锁,也就是里面的类成员变量a;
进过500ms,这个期间线程2进来执行transferMoney()方法,拿到from锁,也就是类成员变量b
接下来500ms之后线程1继续执行,但是他要拿到to锁,也就是他的b成员变量,但是已经被线程1拿过去当他的from锁了
线程2接下来拿他的to锁,也就是a成员变量,但是他已经被线程1拿着了,因为a成员变量是线程1的from锁;
所以进入了死锁的情况
3、模拟多人随机转账
public class MultiTransferMoney {
private static final int NUM_ACCOUNTS = 5000;//账号数
private static final int NUM_MONEY = 1000;//余额
private static final int NUM_ITERATIONS = 10000000;//转账次数
private static final int NUM_THREADS = 20;//转账人数
public static void main(String[] args) {
Random random = new Random();
TransferMoney.Account[] accounts = new TransferMoney.Account[NUM_ACCOUNTS];
//初始化
for (int i = 0; i < accounts.length; i++) {
accounts[i] = new TransferMoney.Account(NUM_MONEY);
}
//转账类
class TransferThread extends Thread{
@Override
public void run() {
for (int i = 0; i < NUM_ITERATIONS; i++) {
//随机下标
int fromAcct = random.nextInt(NUM_ACCOUNTS);
int toAcct = random.nextInt(NUM_ACCOUNTS);
int amount = random.nextInt(NUM_ACCOUNTS);
TransferMoney.transferMoney(accounts[fromAcct], accounts[toAcct],amount);
}
System.out.println("程序结束!!!!");
}
}
//线程数
for (int i = 0; i < NUM_THREADS; i++) {
new TransferThread().start();
}
}
}
运行了很久,发现不再打印,出现死锁!!!
说明:
在多人同时转账的情况下,虽然很人数很多,但只要有发生死锁的风险,就有可能导致,程序陷入死锁
三、死锁发生的必要条件
1、互斥条件
我这里有锁,我拿来了这锁之后,其他线程就不能拿到他,除非我释放了他
2、请求与保持条件
线程1请求一把锁,但是他又保持不释放一把锁
3、不剥夺条件
没有外界干扰结束死锁条件,不会想数据库一样去干扰,仲裁结束
4、循环等待条件
两个线程等待条件,就是你等我释放,我等你释放;
多个线程等待条件,就是A等B,B等C,C等D,D等E,E等F,F等A,无尽不释放,无尽陷入等待;构成环路
以上条件:缺一不可
, 只需要破解以上4个条件任意一个条件,这个死锁就不会发生了!!
四、如何定位死锁的位置
1、jstack命令
通过使用java自带的jstack命令
,来查找我们项目中的死锁问题
${JAVA_HOME}/bin/jstack 8359
#javahome下的jastack命令 进程的pid
执行结果图:↓↓↓
2、ThreadMXBean代码
/******
@author 阿昌
@create 2021-06-01 21:41
*******
* 通过 ThreadMXBean 检测死锁
*/
public class ThreadMXBeanDetection implements Runnable{
int flag = 1;//标记位
static Object lock1 = new Object();
static Object lock2 = new Object();
public static void main(String[] args) throws InterruptedException {
ThreadMXBeanDetection r1 = new ThreadMXBeanDetection();
ThreadMXBeanDetection r2 = new ThreadMXBeanDetection();
r1.flag=1;
r2.flag=0;
Thread thread1 = new Thread(r1);
Thread thread2 = new Thread(r2);
thread1.start();
thread2.start();
Thread.sleep(1000);
//得到实例
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
//发现死锁
if (deadlockedThreads != null && deadlockedThreads.length>0){
//迭代
for (long item : deadlockedThreads) {
//获取线程信息
ThreadInfo threadInfo = threadMXBean.getThreadInfo(item);
//获取死锁线程的名字
System.out.println("发现死锁:"+threadInfo.getThreadName());
}
}
}
@Override
public void run() {
System.out.println("flag= " + flag);
if (flag == 1) {
synchronized (lock1){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2){
System.out.println(flag);
}
}
}
if (flag == 0) {
synchronized (lock2){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1){
System.out.println(flag);
}
}
}
}
}
五、修复死锁策略
1、线上发生死锁应该怎么办
-
线上问题都需要
防范于未然
,不造成损失地扑灭几乎已经是不可能 -
保存案发现场
,然后立即重启服务器 -
暂时保存线上服务器的安全,然后在利用保存的信息,排除死锁,
修改代码
,重新发布
2、常见修复策略
3、转账时避免死锁
-
实际上
不在乎
获取锁的顺序
-
代码演示
通过System.identityHashCode(XXX)
去获取对象的hash值,并进行比较他们的hash值来进行比较来决定拿锁的顺序
当发生hash碰撞时,就再加第三把锁给他们公平竞争
在实际中可以获取主键,来判断接下来的执行顺序,因为主键一般都是不重复唯一的
public class TransferMoney implements Runnable {
Integer flag = 1;
static Account a = new Account(500);
static Account b = new Account(500);
static Object lock = new Object();
//主函数
public static void main(String[] args) throws InterruptedException {
TransferMoney t1 = new TransferMoney();
TransferMoney t2 = new TransferMoney();
t1.flag = 1;
t1.flag = 0;
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t2);
thread1.start();
thread2.start();
thread1.join();
thread1.join();
System.out.println("a的余额为:" + a.balance);
System.out.println("a的余额为:" + b.balance);
}
@Override
public void run() {
if (flag == 1) {
transferMoney(a, b, 200);
}
if (flag == 0) {
transferMoney(b, a, 200);
}
}
//转账
public static void transferMoney(Account from, Account to, int amount) {
//帮助类
class Helper {
public void transfer() {
if (from.balance - amount < 0) {
System.out.println("余额不足");
}
from.balance -= amount;
to.balance += amount;
System.out.println("转账成功");
}
}
//获取对象hash值
int fromHash = System.identityHashCode(from);
int toHash = System.identityHashCode(to);
if (fromHash < toHash) {
synchronized (from) {
synchronized (to) {
new Helper().transfer();
}
}
}
if (fromHash > toHash) {
synchronized (to) {
synchronized (from) {
new Helper().transfer();
}
}
}
//hash碰撞;设置加时赛,公平竞争
if (fromHash == toHash) {
synchronized (lock){
synchronized (to) {
synchronized (from以上是关于Day284&285&286&287.死锁 -Juc的主要内容,如果未能解决你的问题,请参考以下文章
《安富莱嵌入式周报》第286期:8bit浮点数规范,VxWorks火星探测器故障原因修复,Matter V1.0智能家居规范,Wireshark 4.0发布