本文纲要
- 线程安全问题 -- 卖票案例实现
- 线程安全问题 -- 原因分析
- 同步代码块
- 锁对象的唯一性
- 同步方法
Lock锁- 死锁
卖票案例实现
1 ) 需求
模拟电影院售票:全场共 100 张票 ,有 3 个窗口 同时卖票。要求使用多线程来设计程序。
2 ) 基础设计
- 三个线程代表三个窗口。
- 共享数据为 100 张票。
- 使用实现
Runnable接口的方式创建线程,保证多个线程操作同一个任务对象。
3 ) 初始代码(存在安全问题)
下面的代码通过 Runnable 共享一个 Ticket 对象,每个线程在 run() 中循环卖票。为了更真实地模拟出票耗时,我们加入 Thread.sleep(100)。但此时尚未加锁,会暴露线程安全问题。
java
public class Ticket implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket <= 0) {
break;
} else {
try {
Thread.sleep(100); // 模拟出票耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName()
" 在卖票, 还剩下 " + ticket + " 张票");
}
}
}
}
测试类:
java
public class Demo {
public static void main(String[] args) {
Ticket ticket = new Ticket(); // 只创建一个任务对象
Thread t1 = new Thread(ticket, "窗口一");
Thread t2 = new Thread(ticket, "窗口二");
Thread t3 = new Thread(ticket, "窗口三");
t1.start();
t2.start();
t3.start();
}
}
运行结果会出现 重复票 和 负数票 的现象。这是典型的线程安全问题。
原因分析
1 ) 为什么出现重复票?
我们通过下图分析三个线程的执行交错情况。
ticket = 100 线程3 (蓝) 线程2 (红) 线程1 (绿) ticket = 100 线程3 (蓝) 线程2 (红) 线程1 (绿) #mermaid-svg-INuNAzpEuvuvQkPJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-INuNAzpEuvuvQkPJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-INuNAzpEuvuvQkPJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-INuNAzpEuvuvQkPJ .error-icon{fill:#552222;}#mermaid-svg-INuNAzpEuvuvQkPJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-INuNAzpEuvuvQkPJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-INuNAzpEuvuvQkPJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-INuNAzpEuvuvQkPJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-INuNAzpEuvuvQkPJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-INuNAzpEuvuvQkPJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-INuNAzpEuvuvQkPJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-INuNAzpEuvuvQkPJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-INuNAzpEuvuvQkPJ .marker.cross{stroke:#333333;}#mermaid-svg-INuNAzpEuvuvQkPJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-INuNAzpEuvuvQkPJ p{margin:0;}#mermaid-svg-INuNAzpEuvuvQkPJ .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-INuNAzpEuvuvQkPJ text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-INuNAzpEuvuvQkPJ .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-INuNAzpEuvuvQkPJ .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-INuNAzpEuvuvQkPJ .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-INuNAzpEuvuvQkPJ .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-INuNAzpEuvuvQkPJ #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-INuNAzpEuvuvQkPJ .sequenceNumber{fill:white;}#mermaid-svg-INuNAzpEuvuvQkPJ #sequencenumber{fill:#333;}#mermaid-svg-INuNAzpEuvuvQkPJ #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-INuNAzpEuvuvQkPJ .messageText{fill:#333;stroke:none;}#mermaid-svg-INuNAzpEuvuvQkPJ .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-INuNAzpEuvuvQkPJ .labelText,#mermaid-svg-INuNAzpEuvuvQkPJ .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-INuNAzpEuvuvQkPJ .loopText,#mermaid-svg-INuNAzpEuvuvQkPJ .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-INuNAzpEuvuvQkPJ .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-INuNAzpEuvuvQkPJ .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-INuNAzpEuvuvQkPJ .noteText,#mermaid-svg-INuNAzpEuvuvQkPJ .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-INuNAzpEuvuvQkPJ .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-INuNAzpEuvuvQkPJ .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-INuNAzpEuvuvQkPJ .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-INuNAzpEuvuvQkPJ .actorPopupMenu{position:absolute;}#mermaid-svg-INuNAzpEuvuvQkPJ .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-INuNAzpEuvuvQkPJ .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-INuNAzpEuvuvQkPJ .actor-man circle,#mermaid-svg-INuNAzpEuvuvQkPJ line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-INuNAzpEuvuvQkPJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 三个线程都读到 100 多个线程打印的剩余票数都是 97,出现重复 抢到 CPU,读取 ticket=100sleep(100)抢到 CPU,读取 ticket=100sleep(100)抢到 CPU,读取 ticket=100sleep(100)ticket-- → 99准备打印 99(CPU 被抢走)ticket-- → 98(基于 T1 减后的值)准备打印 98(CPU 被抢走)ticket-- → 97打印 97
原因:多个线程在"判断 → sleep → 减减 → 打印"过程中交替执行,导致多个线程读到了同一个 ticket 值,减减后打印出相同的数。
2 ) 为什么出现负数票?
当 ticket = 1 时,三条线程同样可能交错执行。假设流程如下:
ticket = 1 线程3 线程2 线程1 ticket = 1 线程3 线程2 线程1 #mermaid-svg-cgC8z9sGVnelHElW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cgC8z9sGVnelHElW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cgC8z9sGVnelHElW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cgC8z9sGVnelHElW .error-icon{fill:#552222;}#mermaid-svg-cgC8z9sGVnelHElW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cgC8z9sGVnelHElW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cgC8z9sGVnelHElW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cgC8z9sGVnelHElW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cgC8z9sGVnelHElW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cgC8z9sGVnelHElW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cgC8z9sGVnelHElW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cgC8z9sGVnelHElW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cgC8z9sGVnelHElW .marker.cross{stroke:#333333;}#mermaid-svg-cgC8z9sGVnelHElW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cgC8z9sGVnelHElW p{margin:0;}#mermaid-svg-cgC8z9sGVnelHElW .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-cgC8z9sGVnelHElW text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-cgC8z9sGVnelHElW .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-cgC8z9sGVnelHElW .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-cgC8z9sGVnelHElW .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-cgC8z9sGVnelHElW .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-cgC8z9sGVnelHElW #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-cgC8z9sGVnelHElW .sequenceNumber{fill:white;}#mermaid-svg-cgC8z9sGVnelHElW #sequencenumber{fill:#333;}#mermaid-svg-cgC8z9sGVnelHElW #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-cgC8z9sGVnelHElW .messageText{fill:#333;stroke:none;}#mermaid-svg-cgC8z9sGVnelHElW .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-cgC8z9sGVnelHElW .labelText,#mermaid-svg-cgC8z9sGVnelHElW .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-cgC8z9sGVnelHElW .loopText,#mermaid-svg-cgC8z9sGVnelHElW .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-cgC8z9sGVnelHElW .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-cgC8z9sGVnelHElW .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-cgC8z9sGVnelHElW .noteText,#mermaid-svg-cgC8z9sGVnelHElW .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-cgC8z9sGVnelHElW .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-cgC8z9sGVnelHElW .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-cgC8z9sGVnelHElW .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-cgC8z9sGVnelHElW .actorPopupMenu{position:absolute;}#mermaid-svg-cgC8z9sGVnelHElW .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-cgC8z9sGVnelHElW .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-cgC8z9sGVnelHElW .actor-man circle,#mermaid-svg-cgC8z9sGVnelHElW line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-cgC8z9sGVnelHElW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 出现负数票 判断 ticket>0,进入 elsesleep(100)判断 ticket>0,进入 elsesleep(100)判断 ticket>0,进入 elsesleep(100)ticket-- → 0,打印"剩余0张"ticket-- → -1,打印"剩余-1张"ticket-- → -2,打印"剩余-2张"
根本原因 :多条语句在操作共享数据时,未能保证 原子性,被其他线程穿插执行。
同步代码块
1 ) 解决思想
将操作共享数据的代码 锁起来,任一时刻只允许一个线程进入执行,执行完释放锁后下一个线程才能进入。
2 ) 语法格式
java
synchronized(锁对象) {
// 操作共享数据的代码
}
- 锁对象可以是任意对象,但多个线程必须 使用同一把锁。
- 当线程进入同步块,锁自动关闭;执行完代码块,锁自动打开。
3 ) 改进代码
在 Ticket 类中引入一把锁 obj = new Object(),用 synchronized 包裹卖票逻辑:
java
public class Ticket implements Runnable {
private int ticket = 100;
private Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) { // 多个线程使用同一把锁
if (ticket <= 0) {
break; // 卖完结束
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName()
" 在卖票, 还剩下 " + ticket + " 张票");
}
}
}
}
}
测试类不变,此时运行结果不再出现重复票或负数票,线程安全问题得以解决。
项目结构
dir
threadmodule/src/com/wb/threaddemo9/
├── Ticket.java
└── Demo.java
锁对象的唯一性
如果使用 继承 Thread 的方式创建线程,由于每个线程是一个独立对象,使用 this 作为锁就不再唯一。下面演示这个错误及修复方法。
1 ) 错误示例(使用 this 作为锁)
java
public class MyThread extends Thread {
private int ticketCount = 100; // 非静态,各对象独立
@Override
public void run() {
while (true) {
synchronized (this) { // this 是各自不同的线程对象
if (ticketCount <= 0) {
break;
} else {
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
ticketCount--;
System.out.println(getName() + " 在卖票, 还剩下 " + ticketCount + " 张票");
}
}
}
}
}
测试类:
java
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
由于 t1 和 t2 是两个对象,synchronized(this) 实际是两把不同的锁,同步失效,仍会出现重复票和负数票。
2 ) 正确做法:使用静态变量 + 静态锁
java
public class MyThread extends Thread {
private static int ticketCount = 100; // 共享票数
private static Object obj = new Object(); // 唯一的锁
@Override
public void run() {
while (true) {
synchronized (obj) { // 所有线程共用 obj 锁
if (ticketCount <= 0) {
break;
} else {
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
ticketCount--;
System.out.println(getName() + " 在卖票, 还剩下 " + ticketCount + " 张票");
}
}
}
}
}
测试类:
java
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("窗口一");
t2.setName("窗口二");
t1.start();
t2.start();
此时共享数据 ticketCount 和锁 obj 都是 static,所有线程共用同一把锁,线程安全得以保证。
项目结构
dir
threadmodule/src/com/wb/threaddemo10/
├── MyThread.java
└── Demo.java
同步方法
synchronized 还可以直接修饰方法,将整个方法体变为同步代码。
1 ) 同步成员方法
- 锁对象是
this。 - 适用于
Runnable方式,因为多个线程共享同一个Runnable对象。
java
public class MyRunnable implements Runnable {
private int ticketCount = 100;
// 同步方法,锁对象为 this
private synchronized boolean sellTicket() {
if (ticketCount == 0) {
return true; // 卖完
} else {
try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
ticketCount--;
System.out.println(Thread.currentThread().getName()
" 在卖票, 还剩下 " + ticketCount + " 张票");
return false;
}
}
@Override
public void run() {
while (true) {
if ("窗口一".equals(Thread.currentThread().getName())) {
if (sellTicket()) break; // 调用同步方法
}
// ... 窗口二可以使用同步代码块,保证同一把锁(this)
}
}
}
2 ) 同步静态方法
- 锁对象是 类的字节码文件 ,即
类名.class。 - 静态方法中没有
this,只能使用类锁。
示例:
java
private static synchronized boolean synchronizedMethod() {
// 锁对象为 MyRunnable.class
// 卖票逻辑
}
在与同步代码块混用时,若需共用一把锁,则同步块应使用 MyRunnable.class。
java
synchronized (MyRunnable.class) {
// 与静态同步方法使用同一把锁
}
测试类:
java
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr, "窗口一");
Thread t2 = new Thread(mr, "窗口二");
t1.start();
t2.start();
此时同步方法和同步代码块使用的都是同一把锁(对象锁 this 或类锁 .class),线程安全。
3 ) 项目结构
dir
threadmodule/src/com/wb/threaddemo11/
├── MyRunnable.java
└── Demo.java
Lock 锁
JDK 5 起提供了更灵活的锁接口 java.util.concurrent.locks.Lock。
可手动控制 加锁 (lock()) 和 释放锁 (unlock())。
为避免异常导致锁无法释放,unlock() 通常放在 finally 块中。
1 ) 使用 ReentrantLock
java
import java.util.concurrent.locks.ReentrantLock;
public class Ticket implements Runnable {
private int ticket = 100;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock(); // 手动加锁
if (ticket <= 0) {
break;
} else {
Thread.sleep(100);
ticket--;
System.out.println(Thread.currentThread().getName()
" 在卖票, 还剩下 " + ticket + " 张票");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 手动释放,务必执行
}
}
}
}
测试类与同步代码块方式相同,运行效果一致,线程安全。
2 ) 与 synchronized 对比
| 特性 | synchronized | Lock |
|---|---|---|
| 加锁/释放 | 自动 | 手动 (lock() / unlock()) |
| 灵活性 | 较低 | 可中断、可尝试获取锁 |
| 异常处理 | 自动释放 | 必须在 finally 中释放 |
| 出现版本 | Java 早期 | JDK 5 |
3 ) 项目结构
dir
threadmodule/src/com/wb/threaddemo12/
├── Ticket.java
└── Demo.java
死锁
当多个线程相互持有对方所需的锁时,会产生 死锁,所有线程永久阻塞。
1 ) 死锁产生的条件
- 有多个锁(如 A 锁、B 锁)。
- 线程1 先获取 A 锁,再试图获取 B 锁;线程2 先获取 B 锁,再试图获取 A 锁。
#mermaid-svg-iZhYfwtOGiZ80CQ7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-iZhYfwtOGiZ80CQ7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .error-icon{fill:#552222;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .marker.cross{stroke:#333333;}#mermaid-svg-iZhYfwtOGiZ80CQ7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-iZhYfwtOGiZ80CQ7 p{margin:0;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .cluster-label text{fill:#333;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .cluster-label span{color:#333;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .cluster-label span p{background-color:transparent;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .label text,#mermaid-svg-iZhYfwtOGiZ80CQ7 span{fill:#333;color:#333;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .node rect,#mermaid-svg-iZhYfwtOGiZ80CQ7 .node circle,#mermaid-svg-iZhYfwtOGiZ80CQ7 .node ellipse,#mermaid-svg-iZhYfwtOGiZ80CQ7 .node polygon,#mermaid-svg-iZhYfwtOGiZ80CQ7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .rough-node .label text,#mermaid-svg-iZhYfwtOGiZ80CQ7 .node .label text,#mermaid-svg-iZhYfwtOGiZ80CQ7 .image-shape .label,#mermaid-svg-iZhYfwtOGiZ80CQ7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .rough-node .label,#mermaid-svg-iZhYfwtOGiZ80CQ7 .node .label,#mermaid-svg-iZhYfwtOGiZ80CQ7 .image-shape .label,#mermaid-svg-iZhYfwtOGiZ80CQ7 .icon-shape .label{text-align:center;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .node.clickable{cursor:pointer;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .arrowheadPath{fill:#333333;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-iZhYfwtOGiZ80CQ7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iZhYfwtOGiZ80CQ7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-iZhYfwtOGiZ80CQ7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .cluster text{fill:#333;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .cluster span{color:#333;}#mermaid-svg-iZhYfwtOGiZ80CQ7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-iZhYfwtOGiZ80CQ7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .icon-shape,#mermaid-svg-iZhYfwtOGiZ80CQ7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .icon-shape p,#mermaid-svg-iZhYfwtOGiZ80CQ7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .icon-shape .label rect,#mermaid-svg-iZhYfwtOGiZ80CQ7 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iZhYfwtOGiZ80CQ7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-iZhYfwtOGiZ80CQ7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-iZhYfwtOGiZ80CQ7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 等待
等待
线程1 持有 A 锁
B锁
线程2 持有 B 锁
A锁
2 ) 死锁代码演示
java
public class DeadLockDemo {
public static void main(String[] args) {
Object objA = new Object();
Object objB = new Object();
new Thread(() -> {
while (true) {
synchronized (objA) {
synchronized (objB) {
System.out.println("线程1 正在运行");
}
}
}
}).start();
new Thread(() -> {
while (true) {
synchronized (objB) {
synchronized (objA) {
System.out.println("线程2 正在运行");
}
}
}
}).start();
}
}
运行后程序将卡死,两个线程都无法继续执行。
3 ) 避免死锁
- 尽量减少锁的嵌套。
- 如果需要多个锁,确保所有线程 按相同的顺序 获取锁。
4 ) 项目结构
dir
threadmodule/src/com/wb/threaddemo13/
└── Demo.java
总结
- 线程安全问题源于多个线程并发操作共享数据。
- 解决方式:将操作共享数据的代码变为 原子操作,即加锁。
- Java 提供了:
synchronized同步代码块(灵活指定锁对象)synchronized同步方法(成员方法锁this,静态方法锁类.class)Lock接口(ReentrantLock)实现手动控制锁
- 锁对象的 唯一性 是线程安全的前提。
- 死锁由 锁嵌套 导致,开发中应避免。