1. 背景引入
场景问题:
"假设一个景区售票系统,总票数100张,10个窗口同时售票。如果每个窗口的售票代码直接执行
tickets--
,最终会卖出多少张票?"
互动提问:
- "可能出现超卖(卖出超过100张)吗?为什么?"
- "如何保证每个窗口看到的剩余票数是真实的?"
2. 基础知识讲解
(1)线程的创建方式(代码对比)
java
复制
scala
// 方式1:继承Thread类(不推荐,Java单继承限制)
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running");
}
}
new MyThread().start();
// 方式2:实现Runnable接口(推荐)
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("Runnable running");
}
};
new Thread(task).start();
// 方式3:Lambda表达式(Java8+简化)
new Thread(() -> System.out.println("Lambda thread")).start();
(2)线程生命周期(图示+说明)
plaintext
复制
scss
新建(New) → 就绪(Runnable) → 运行(Running) → 阻塞(Blocked) → 终止(Terminated)
-
关键状态转换:
start()
触发就绪,由JVM调度进入运行。sleep()
/wait()
进入阻塞,notify()
/interrupt()
恢复就绪。
(3)线程安全问题的根源
- 竞态条件 :多个线程对共享资源的非原子操作(如
tickets--
)。 - 内存可见性:线程本地缓存导致数据更新不同步。
java
复制
ini
// tickets--的非原子性分解步骤:
int temp = tickets; // Step1: 读取共享变量
temp = temp - 1; // Step2: 修改临时变量
tickets = temp; // Step3: 写回共享变量
3. 案例演示:景区售票问题
(1)问题复现(未同步版本)
java
复制
csharp
class TicketSystem {
private int tickets = 100;
public void sellTicket() {
while (tickets > 0) {
try {
Thread.sleep(10); // 模拟网络延迟
} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + "售出第" + tickets-- + "张票");
}
}
}
public static void main(String[] args) {
TicketSystem system = new TicketSystem();
for (int i = 0; i < 10; i++) {
new Thread(system::sellTicket, "窗口" + i).start();
}
}
// 运行结果:可能输出"售出第0张票"或重复票号
(2)解决方案1:synchronized同步方法
java
复制
arduino
public synchronized void sellTicket() { ... } // 锁对象为this
(3)解决方案2:显式Lock锁(更灵活)
java
复制
java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class TicketSystem {
private Lock lock = new ReentrantLock();
public void sellTicket() {
lock.lock();
try {
// 临界区代码
} finally {
lock.unlock(); // 确保锁释放
}
}
}
(4)解决方案3:volatile(仅解决可见性,不解决原子性)
java
复制
arduino
private volatile int tickets = 100;
// 仅适用于单一写操作场景(如状态标志位),不能替代锁!
4. 学员练习
任务 :修改以下计数器代码,保证多线程下count
最终值为10000。
java
复制
csharp
class UnsafeCounter {
private int count = 0;
public void add() { count++; }
}
// 创建10个线程,每个线程调用add()1000次
提示:
- 可选方案:synchronized方法、ReentrantLock、AtomicInteger。
5. 课后作业
- 基础题:用两种线程创建方式实现"交替打印A/B",如A1→B1→A2→B2...
- 提高题:通过wait()/notify()实现生产者-消费者模型(缓冲区容量为5)。