Appearance
第23课:多线程基础
🎯 学习目标
- 理解进程与线程的区别
- 掌握创建线程的三种方式
- 理解线程的生命周期
- 掌握线程的常用方法
- 理解守护线程
📖 一、进程与线程
C vs Java 多线程
C 语言(pthread):
c
#include <pthread.h>
void* thread_func(void* arg) {
printf("Thread running\n");
return NULL;
}
int main() {
pthread_t thread;
pthread_create(&thread, NULL, thread_func, NULL);
pthread_join(thread, NULL);
}Java(内置支持):
java
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("Thread running");
});
thread.start();
}
}📖 二、创建线程的三种方式
方式1:继承 Thread 类
java
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
// 使用
public class Main {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start(); // 启动线程
t2.start();
}
}方式2:实现 Runnable 接口(推荐)
java
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
// 使用
public class Main {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread t1 = new Thread(runnable, "Thread-1");
Thread t2 = new Thread(runnable, "Thread-2");
t1.start();
t2.start();
}
}
// Lambda 简化写法
Thread t = new Thread(() -> {
System.out.println("Thread running");
});
t.start();方式3:实现 Callable 接口(有返回值)
java
import java.util.concurrent.*;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
}
// 使用
public class Main {
public static void main(String[] args) throws Exception {
MyCallable callable = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(callable);
Thread thread = new Thread(task);
thread.start();
// 获取返回值(阻塞)
Integer result = task.get();
System.out.println("Result: " + result); // 5050
}
}三种方式对比
| 方式 | 优点 | 缺点 | 使用场景 |
|---|---|---|---|
| 继承 Thread | 简单直观 | 单继承限制 | 简单任务 |
| 实现 Runnable | 灵活,可复用 | 无返回值 | 大多数场景 |
| 实现 Callable | 有返回值,可抛异常 | 复杂 | 需要返回结果 |
📖 三、线程的生命周期
NEW(新建)
↓ start()
RUNNABLE(可运行)
↓ 获得CPU
RUNNING(运行中)
↓ sleep/wait/阻塞IO
BLOCKED/WAITING/TIMED_WAITING(阻塞/等待)
↓ 唤醒/超时/IO完成
RUNNABLE
↓ run()结束
TERMINATED(终止)java
Thread thread = new Thread(() -> {
System.out.println("Running");
});
System.out.println(thread.getState()); // NEW
thread.start();
System.out.println(thread.getState()); // RUNNABLE
Thread.sleep(100);
System.out.println(thread.getState()); // TERMINATED📖 四、Thread 常用方法
1. 启动与停止
java
// start() - 启动线程
thread.start();
// ❌ 不要直接调用 run()
thread.run(); // 这只是普通方法调用,不会创建新线程
// stop() - 已废弃,强制停止线程(不安全)
// 正确的停止方式:使用标志位
public class MyThread extends Thread {
private volatile boolean running = true;
@Override
public void run() {
while (running) {
// 执行任务
}
}
public void stopThread() {
running = false;
}
}2. 线程休眠
java
// sleep() - 让当前线程休眠(不释放锁)
try {
Thread.sleep(1000); // 休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
// 应用:定时任务
while (true) {
System.out.println("执行任务");
Thread.sleep(5000); // 每5秒执行一次
}3. 线程让步
java
// yield() - 暂停当前线程,让其他线程执行
Thread.yield();
// 示例
public class YieldDemo extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(getName() + ": " + i);
if (i == 2) {
Thread.yield(); // 让出CPU
}
}
}
}4. 线程等待
java
// join() - 等待线程结束
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("T1: " + i);
}
});
t1.start();
t1.join(); // 主线程等待 t1 结束
System.out.println("T1 finished");
// join(timeout) - 最多等待指定时间
t1.join(1000); // 最多等1秒5. 中断线程
java
// interrupt() - 中断线程
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("Running...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Interrupted");
break; // 响应中断
}
}
});
thread.start();
Thread.sleep(3000);
thread.interrupt(); // 中断线程6. 线程优先级
java
// setPriority() - 设置优先级(1-10)
Thread t1 = new Thread(() -> { });
t1.setPriority(Thread.MAX_PRIORITY); // 10
t1.setPriority(Thread.NORM_PRIORITY); // 5(默认)
t1.setPriority(Thread.MIN_PRIORITY); // 1
// 注意:优先级只是建议,不保证一定先执行📖 五、守护线程
java
// 守护线程(Daemon Thread):后台服务线程
// 当所有非守护线程结束时,守护线程自动结束
Thread daemon = new Thread(() -> {
while (true) {
System.out.println("守护线程运行中...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
});
daemon.setDaemon(true); // 必须在 start() 之前设置
daemon.start();
// 主线程结束后,守护线程也会结束
// 典型应用:
// - 垃圾回收线程
// - JIT 编译线程
// - 定时器线程📖 六、线程安全问题
1. 竞态条件示例
java
public class Counter {
private int count = 0;
public void increment() {
count++; // 不是原子操作!
// 实际上分为3步:
// 1. 读取 count 的值
// 2. 加 1
// 3. 写回 count
}
}
// 多线程访问会出现问题
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(counter.getCount()); // 结果可能小于2000!2. 解决方案预告
下一课将学习:
- synchronized 关键字
- Lock 接口
- volatile 关键字
- 原子类
⚠️ 常见陷阱
陷阱1:直接调用 run
thread.run() 只是普通方法调用,不会启动新线程。启动线程必须调用 thread.start()。
陷阱2:忽略中断
捕获 InterruptedException 后直接吞掉,会让线程无法响应取消。应恢复中断标记或退出。
陷阱3:共享可变状态无保护
多个线程同时读写共享变量,如果没有同步、volatile 或并发工具,结果不可预测。
陷阱4:把 sleep 当同步工具
sleep 只能暂停当前线程,不能保证另一个线程已经完成。线程协作应使用 join、CountDownLatch、Future 等机制。
🆚 Java vs C 对比
| 特性 | C pthread | Java Thread |
|---|---|---|
| 创建线程 | pthread_create | new Thread(...).start |
| 等待结束 | pthread_join | thread.join |
| 取消协作 | 标志位/信号 | interrupt 协作中断 |
| 共享内存 | 手动同步 | synchronized/Lock/volatile/JUC |
对 C 程序员来说,Java 线程也是 OS 线程抽象,但 Java 提供了更完整的内存模型、同步关键字和并发工具库。
💡 最佳实践
- 优先使用 Runnable/Callable(避免单继承限制)
- 不要调用 stop()(使用标志位停止)
- 响应中断(在循环中检查 isInterrupted())
- 合理设置优先级(不要过度依赖)
- 注意线程安全(多线程访问共享数据要同步)
📝 练习
完成 练习/Ex23_Thread_Basics.java:
- 三种方式创建线程
- 线程生命周期观察
- 线程方法应用
- 守护线程实践
- 线程安全问题演示
- 综合:多线程下载器
🎓 下一步
- 第24课:线程同步 - synchronized、Lock、volatile