一、多线程基础
1. 核心概念
1.1 进程 vs 线程
-
进程:操作系统分配资源的基本单位。例如,运行一个Java程序就是一个进程,拥有独立的内存空间(堆、方法区等)。
-
线程:进程内的执行单元。一个进程可以有多个线程,共享进程的内存资源(如堆内存),但每个线程有自己的程序计数器、栈、本地方法栈。
比喻:进程像一家公司,线程是公司里的员工,共享办公室(内存资源),但各自处理不同任务。
1.2 并发 vs 并行
-
并发(Concurrency) :单核CPU通过时间片轮转模拟“同时执行”,线程交替运行。
// 示例:单核CPU交替执行线程A和线程B ThreadA.start(); ThreadB.start(); -
并行(Parallelism) :多核CPU真正同时执行多个线程。
// 示例:四核CPU可同时运行4个线程
1.3 为什么需要多线程?
- 提高吞吐量:例如,Web服务器用多线程同时处理多个请求。
- 提升响应速度:例如,GUI程序用后台线程处理耗时任务,避免主线程卡死。
- 充分利用多核CPU:现代CPU多为多核,多线程可发挥硬件性能。
2. 线程的创建方式
Java中创建线程的3种方式,各有适用场景:
2.1 继承Thread类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running: " + Thread.currentThread().getName());
}
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程,JVM调用run()
}
- 缺点:Java是单继承,若类已继承其他类,无法再继承
Thread。
2.2 实现Runnable接口(推荐)
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable running: " + Thread.currentThread().getName());
}
}
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
}
- 优点:避免单继承限制,任务与线程解耦,适合多线程共享同一任务。
2.3 实现Callable接口(带返回值)
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return xxx; // 返回计算结果
}
}
public static void main(String[] args) throws Exception {
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start();
int result = futureTask.get(); // 阻塞直到获取结果
System.out.println("Result: " + result);
}
- 适用场景:需要异步获取执行结果的场景(如计算密集型任务)。
3. 线程的生命周期
线程从创建到销毁经历多个状态,可通过Thread.getState()查看当前状态:
3.1 生命周期状态图
新建(New) → 就绪(Runnable) → 运行(Running) → 阻塞(Blocked) → 等待(Waiting) → 超时等待(Timed Waiting) → 终止(Terminated)
3.2 各状态详解
- New:线程对象已创建,但未调用
start()。 - Runnable:调用
start()后进入就绪状态,等待CPU调度。 - Running:线程获得CPU时间片,执行
run()方法。 - Blocked:线程等待获取锁(如进入
synchronized代码块时锁被占用)。 - Waiting:线程主动调用
wait()、join()或LockSupport.park(),需其他线程唤醒。 - Timed Waiting:调用
sleep(ms)、wait(timeout)等方法,超时后自动恢复。 - Terminated:
run()执行完毕或发生未捕获异常。
4. 线程控制方法
4.1 核心方法
-
start():启动线程,进入就绪状态(只能调用一次)。 -
run():线程实际执行的代码,直接调用run()不会启动新线程! -
join():等待线程结束。Thread thread = new Thread(() -> { System.out.println("子线程执行"); }); thread.start(); thread.join(); // 主线程等待子线程结束 System.out.println("主线程继续"); -
sleep(long ms):线程休眠指定毫秒,不释放锁。try { Thread.sleep(1000); // 休眠1秒 } catch (InterruptedException e) { e.printStackTrace(); } -
yield():提示调度器让出当前CPU时间片,但调度器可能忽略。
4.2 中断机制
interrupt():设置线程的中断标志(并非强制终止线程)。isInterrupted():检查中断标志是否被设置。Thread.interrupted():检查并清除中断标志。
正确的中断处理方式:
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// 正常执行任务
}
System.out.println("线程被中断,优雅退出");
});
thread.start();
// 主线程中断子线程
thread.interrupt();
5. 简单示例:多线程执行
public class BasicThreadExample {
public static void main(String[] args) {
// 创建两个线程
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Thread-1: " + i);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Thread-2: " + i);
}
});
// 启动线程
thread1.start();
thread2.start();
// 输出结果可能是交替的,取决于线程调度
}
}
可能的输出:
Thread-1: 0
Thread-2: 0
Thread-1: 1
Thread-2: 1
...
说明:线程执行顺序由操作系统调度器决定,结果可能每次不同。
关键点总结
- 线程是轻量级执行单元,共享进程资源,适合处理并发任务。
- 优先使用
Runnable或Callable,避免继承局限性。 - 理解线程状态转换,尤其是阻塞和等待的区别。
- 正确使用中断机制,避免用已废弃的
stop()方法。