多线程

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)。
相关推荐
Victor35614 小时前
https://editor.csdn.net/md/?articleId=139321571&spm=1011.2415.3001.9698
后端
Victor35614 小时前
Hibernate(89)如何在压力测试中使用Hibernate?
后端
灰子学技术15 小时前
go response.Body.close()导致连接异常处理
开发语言·后端·golang
Gogo81616 小时前
BigInt 与 Number 的爱恨情仇,为何大佬都劝你“能用 Number 就别用 BigInt”?
后端
fuquxiaoguang16 小时前
深入浅出:使用MDC构建SpringBoot全链路请求追踪系统
java·spring boot·后端·调用链分析
毕设源码_廖学姐17 小时前
计算机毕业设计springboot招聘系统网站 基于SpringBoot的在线人才对接平台 SpringBoot驱动的智能求职与招聘服务网
spring boot·后端·课程设计
野犬寒鸦18 小时前
从零起步学习并发编程 || 第六章:ReentrantLock与synchronized 的辨析及运用
java·服务器·数据库·后端·学习·算法
逍遥德19 小时前
如何学编程之01.理论篇.如何通过阅读代码来提高自己的编程能力?
前端·后端·程序人生·重构·软件构建·代码规范
MX_935920 小时前
Spring的bean工厂后处理器和Bean后处理器
java·后端·spring
程序员泠零澪回家种桔子21 小时前
Spring AI框架全方位详解
java·人工智能·后端·spring·ai·架构