Java 多线程中的死锁概述
死锁
死锁的定义
发生在并发中
当两个线程(或更多)线程(或线程)相互持有对方所需要的资源,又不主动释放,导致所有线程都无法继续执行,是程序陷入无尽的阻塞,这就是死锁。
如果多个线程之间的依赖关系是环形,存在环形的锁的依赖关系,那么也可能会发生死锁。
死锁的影响
死锁的影响在不同的系统中是不一样的,这取决于系统对死锁的处理能力。
- 数据库中:检测并放弃事务;
- JVM中:无法自动处理,但是提供了工具可以帮助我们取检测;
程序中的死锁
- 一旦发生,多是高并发场景,影响用户多;
- 整个系统崩溃,子系统崩溃,性能降低;
- 压力测试无法找出所有潜在的死锁;
例子
例一
public class MustDeadLock extends Thread {
int flag = 1;
static Object o1 = new Object();
static Object o2 = new Object();
public static void main(String[] args) {
MustDeadLock run1 = new MustDeadLock();
MustDeadLock run2 = new MustDeadLock();
run1.flag = 1;
run2.flag = 0;
run1.start();
run2.start();
}
@Override
public void run() {
System.out.println("flag = " + flag);
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("线程1成功拿到两把锁!");
}
}
}
if (flag == 0) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("线程2成功拿到两把锁!");
}
}
}
}
}
案例分析
- 当类的对象 flag=1 时(T1),先锁定 O1,睡眠 500 毫秒,然后锁定 O2;
- T1 在睡眠的过程中,另一个 flag=0(T2)线程启动,先锁定 O2,睡眠 500 毫秒,等待 T1 释放 O1;
- T1 睡眠结束后需要锁定 O2 才能继续执行,而此时 O2 已被 T2 锁定;
- T2 睡眠结束后需要锁定 O1 才能继续执行,而此时 O1 已被 T1 锁定;
- 此时 T1,T2 相互等待,都需要对方锁定的资源才能继续执行,于是便发生死锁了。
例二(转账操作)
public class TransferMoney implements Runnable {
int flag = 1;
static Account a = new Account(500);
static Account b = new Account(500);
public static void main(String[] args) throws InterruptedException {
TransferMoney r1 = new TransferMoney();
TransferMoney r2 = new TransferMoney();
r1.flag = 1;
r2.flag = 0;
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("a.balance = " + a.balance);
System.out.println("b.balance = " + 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) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (to) {
if (from.balance - to.balance < 0) {
System.out.println("余额不足,转账失败!");
}
from.balance -= to.balance;
to.balance += from.balance;
System.out.println("转账成功,转账共:" + amount);
}
}
}
static class Account {
int balance;
public Account(int balance) {
this.balance = balance;
}
}
}
死锁产生的必要条件
产生死锁必须同时满足以下四个条件,只要其中一条不成立,死锁就不会发生。
① 互斥条件
进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
② 请求与保持条件
进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
③ 不剥夺条件
进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
④ 循环等待条件
存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, ..., pn},其中Pi等 待的资源被P(i+1)占有(i=0, 1, ..., n-1),Pn等待的资源被P0占有,如例一所示。
如何定位死锁?
jstack
这里以案例一为基础进行展示。
第一步:先运行列一;
第二步:找到 Java 在系统中的进程 id;
方式一(直接通过任务管理器获取)
我是在 Windows 环境下进行演示,我可以先打开任务管理,然后再运行例一;
找到对应的进行 id;
方式二(通过 Java 提供的程序获取)
找到 Java JDK 的安装路径下的 bin 目录;
在此目录下打开 CMD 窗口;
然后直接运行 jps 后就会打印出我们正在运行的进程 id;
D:\development\jdk\1.8jdk\bin>jps
12160 Jps
45224 Launcher
23020 MustDeadLock
第三步:通过工具定位到死锁
还是在 Java JDK 的安装路径下的 bin 目录下打开 cmd 窗口,然后运行 jstack + 进程id
D:\development\jdk\1.8jdk\bin>jstack 23020
2022-03-06 17:13:11
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.281-b09 mixed mode):
"DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x00000204548cb000 nid=0x12220 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Thread-1" #13 prio=5 os_prio=0 tid=0x00000204719b6000 nid=0xbd44 waiting for monitor entry [0x0000000c528ff000]
java.lang.Thread.State: BLOCKED (on object monitor)
at main.threaddemo.MustDeadLock.run(MustDeadLock.java:45)
- waiting to lock <0x000000076c09a3a0> (a java.lang.Object)
- locked <0x000000076c09a3b0> (a java.lang.Object)
"Thread-0" #12 prio=5 os_prio=0 tid=0x00000204719b3000 nid=0xd260 waiting for monitor entry [0x0000000c527ff000]
java.lang.Thread.State: BLOCKED (on object monitor)
at main.threaddemo.MustDeadLock.run(MustDeadLock.java:33)
- waiting to lock <0x000000076c09a3b0> (a java.lang.Object)
- locked <0x000000076c09a3a0> (a java.lang.Object)
"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x000002047198c800 nid=0x48f4 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x00000204718eb800 nid=0xa35c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x00000204718e6800 nid=0x9288 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x00000204718e4000 nid=0x7dcc waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x00000204718e1000 nid=0x101a4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x00000204718de000 nid=0xb3b4 runnable [0x0000000c520fe000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
- locked <0x000000076bf8f8a0> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
- locked <0x000000076bf8f8a0> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:47)
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x0000020471893000 nid=0x109e0 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000020471892800 nid=0x6798 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000002046f4cb000 nid=0x5848 in Object.wait() [0x0000000c51dfe000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076be08ee0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x000000076be08ee0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000002046f4c4800 nid=0xee60 in Object.wait() [0x0000000c51cff000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076be06c00> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x000000076be06c00> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=2 tid=0x000002046f499000 nid=0x7284 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00000204548e1800 nid=0xf530 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00000204548e3000 nid=0x12aa0 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00000204548e4000 nid=0x11d58 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00000204548e5800 nid=0x67d8 runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x00000204548e7800 nid=0xed20 runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x00000204548e8800 nid=0x2e10 runnable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x00000204548eb800 nid=0xd504 runnable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x00000204548ec800 nid=0x10cc0 runnable
"GC task thread#8 (ParallelGC)" os_prio=0 tid=0x00000204548ed800 nid=0x8548 runnable
"GC task thread#9 (ParallelGC)" os_prio=0 tid=0x00000204548ee800 nid=0xac70 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x00000204719b1000 nid=0x4b64 waiting on condition
JNI global references: 12
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x000002046f4c8368 (object 0x000000076c09a3a0, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x000002046f4caca8 (object 0x000000076c09a3b0, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at main.threaddemo.MustDeadLock.run(MustDeadLock.java:45)
- waiting to lock <0x000000076c09a3a0> (a java.lang.Object)
- locked <0x000000076c09a3b0> (a java.lang.Object)
"Thread-0":
at main.threaddemo.MustDeadLock.run(MustDeadLock.java:33)
- waiting to lock <0x000000076c09a3b0> (a java.lang.Object)
- locked <0x000000076c09a3a0> (a java.lang.Object)
Found 1 deadlock.
运行后主要观察
Java stack information for the threads listed above:
===================================================
"Thread-1":
at main.threaddemo.MustDeadLock.run(MustDeadLock.java:45)
- waiting to lock <0x000000076c09a3a0> (a java.lang.Object)
- locked <0x000000076c09a3b0> (a java.lang.Object)
"Thread-0":
at main.threaddemo.MustDeadLock.run(MustDeadLock.java:33)
- waiting to lock <0x000000076c09a3b0> (a java.lang.Object)
- locked <0x000000076c09a3a0> (a java.lang.Object)
Found 1 deadlock.
通过 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 t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
Thread.sleep(3000);
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]);
System.out.println("发现死锁" + threadInfo.getThreadName());
}
}
}
@Override
public void run() {
System.out.println("flag = " + flag);
if (flag == 1) {
synchronized (lock1) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("线程1成功拿到两把锁!");
}
}
}
if (flag == 0) {
synchronized (lock2) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("线程2成功拿到两把锁!");
}
}
}
}
}
输出
flag = 1
flag = 0
发现死锁Thread-1
发现死锁Thread-0
常见的修复方式
①避免策略
避免相反的获取锁的顺序,也就是在编写程序的时候就规划好锁的获取,从而破坏死锁产生的四个必要条件的其中一个。
②检测与恢复策略
检测到锁的时候再将其恢复,不过这个时候已经产生了一定的影响了。
实际开发中如何避免死锁
- 设置超时时间;
- 多使用并发类而不是自己设计锁;
- 尽量降低锁的使用粒度:用不同的锁而不是一个锁;
- 如果能使用同步代码块,就不使用同步方法:自已指定所对象;
- 创建线程的时候命名尽量达到见名知义,方便后面排查问题;
Java 多线程中的死锁概述的更多相关文章
- Java多线程中的死锁
Java多线程中的死锁 死锁产生的原因 线程死锁是指由两个以上的线程互相持有对方所需要的资源,导致线程处于等待状态,无法往前执行. 当线程进入对象的synchronized代码块时,便占有了资源,直到 ...
- java多线程中的死锁情况读书笔记
多线程中的死锁 在前面的分析中,我们知道一个对象可以用Synchronized方法或者其他的加锁形式来防止别的任务在互斥还没有释放的时候就访问这个对象. 试想一下这样的情况:某个任务在等待另一个任务, ...
- Java多线程中的死锁问题
Java程序基本都要涉及到多线程,而在多线程环境中不可避免的要遇到线程死锁的问题.Java不像数据库那么能够检测到死锁,然后进行处理,Java中的死锁问题,只能通过程序员自己写代码时避免引入死锁的可能 ...
- java多线程中的死锁、活锁、饥饿、无锁都是什么鬼?
死锁.活锁.饥饿是关于多线程是否活跃出现的运行阻塞障碍问题,如果线程出现了这三种情况,即线程不再活跃,不能再正常地执行下去了. 死锁 死锁是多线程中最差的一种情况,多个线程相互占用对方的资源的锁,而又 ...
- Java多线程——线程的死锁
Java多线程——线程的死锁 摘要:本文主要介绍了Java多线程中遇到的死锁问题. 部分内容来自以下博客: https://www.cnblogs.com/wy697495/p/9757982.htm ...
- java多线程中的三种特性
java多线程中的三种特性 原子性(Atomicity) 原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行. 如果一个操作时原子性的,那么多线程并 ...
- java 多线程中的wait方法的详解
java多线程中的实现方式存在两种: 方式一:使用继承方式 例如: PersonTest extends Thread{ String name; public PersonTest(String n ...
- java多线程中并发集合和同步集合有哪些?区别是什么?
java多线程中并发集合和同步集合有哪些? hashmap 是非同步的,故在多线程中是线程不安全的,不过也可以使用 同步类来进行包装: 包装类Collections.synchronizedMap() ...
- java多线程中最佳的实践方案是什么?
java多线程中最佳的实践方案是什么? 给你的线程起个有意义的名字.这样可以方便找bug或追踪.OrderProcessor, QuoteProcessor or TradeProcessor 这种名 ...
随机推荐
- MybatisPlus多表连接查询
一.序言 (一)背景内容 软件应用技术架构中DAO层最常见的选型组件为MyBatis,熟悉MyBatis的朋友都清楚,曾几何时MyBatis是多么的风光,使用XML文件解决了复杂的数据库访问的难题.时 ...
- bom-location
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- RPC原理及RPC实例分析(转)
出处:https://my.oschina.net/hosee/blog/711632 在学校期间大家都写过不少程序,比如写个hello world服务类,然后本地调用下,如下所示.这些程序的特点是服 ...
- Myeclipse设置关键词自动提示功能以及取消空格和"="的自动补全
一.设置Myeclipse从a~z的自动提示功能 1. "window"→"Preferences"2. 选择"Java",展开," ...
- Saas系统架构的思考,多租户Saas架构设计分析
ToB Saas系统最近几年都很火.很多创业公司都在尝试创建企业级别的应用 cRM, HR,销售, Desk Saas系统.很多Saas创业公司也拿了大额风投.毕竟Saas相对传统软件的优势非常明显. ...
- 宝塔面板部署springboot项目并使用域名访问
环境准备:服务器搭建宝塔linux面板项目: springboot项目项目打包方式jar包 环境: 一.服务器,一个域名,然后再服务器上(DNS服务)将域名解析好.把安全组设置好. 二.用宝塔快速搭建 ...
- Linux专项之Apache
day01:apache服务框架 前期准备 1.虚拟机上网 实验一:展示默认页面 1.安装软件(httpd) yum search httpd yum install -y httpd 2.关闭防火墙 ...
- 02网络编程( socket套接字+TCP粘包 )
目录 02 网络编程 一.socket套接字编程 二.简易代码模板 2.1 服务端 2.2 客户端 三.通信循环及代码优化 四.黏包现象 五.struct模块 六.简易版本报头 七.上传文件数据 * ...
- MXNet学习:预测结果-识别单张图片
用到了model里的FeedForward.load和predict import os import mxnet as mx import numpy as np import Image from ...
- Solution -「HDU #6566」The Hanged Man
\(\mathcal{Description}\) Link. 给定一棵含 \(n\) 个点的树,每个结点有两个权值 \(a\) 和 \(b\).对于 \(k\in[1,m]\),分别求 \[ ...