元数据卡
- 前置知识:方法与方法栈(第5章)
- 预计时间:10 分钟
- 完成标志:能创建并启动线程,理解竞态条件的概念
为什么需要并发
你的程序一次只能做一件事——读文件的时候不能同时处理用户请求。但现实生活中,我们期望程序"同时"做多件事:下载文件的同时还能继续浏览页面。
并发就是程序同时处理多个任务的能力。在 Java 中,最基本的单位是线程。
创建线程两种方式
java
// 方式一:继承 Thread
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程运行中");
}
}
new MyThread().start();
// 方式二:实现 Runnable(更推荐——不占用继承)
Runnable task = () -> {
System.out.println("线程运行中");
};
new Thread(task).start();推荐实现 Runnable,因为 Java 只允许单继承,extends Thread 就没办法继承其他类了。
线程的生命周期
新建 (New) → 就绪 (Runnable) → 运行 (Running) → 死亡 (Terminated)
↓
阻塞 (Blocked/Waiting)java
Thread t = new Thread(task); // 新建
t.start(); // 就绪→运行
t.join(); // 等待 t 执行完毕竞态条件
多个线程同时访问共享数据时可能出问题:
java
class Counter {
private int count = 0;
public void increment() {
count++; // 不是原子操作!
}
}两个线程同时执行 count++,本来期望加 2,实际可能只加了 1。因为 count++ 实际上是三步:读 count → 加 1 → 写回 count。两步之间的间隙,另一个线程插了进来。
加锁保护
java
class SafeCounter {
private int count = 0;
public synchronized void increment() {
count++; // synchronized 保证同时只有一个线程能执行
}
}synchronized 关键字给方法加锁——同一时刻只有一个线程能进入这个方法。其他线程在外面排队。
常见陷阱
Thread.sleep() 不释放锁!sleep 期间线程仍然持有锁,其他线程进不来。
旅人笔记
线程让程序"同时"做多件事。实现 Runnable 比继承 Thread 更灵活。多个线程读写共享数据需要同步——用 synchronized 加锁保护。竞态条件是并发的头号敌人。
→ 下一步:同步与竞态条件
创建线程只是第一步。多个线程操作共享数据会出什么问题?下一节看 synchronized 怎么管住并发。