Skip to content

第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 pthreadJava Thread
创建线程pthread_createnew Thread(...).start
等待结束pthread_jointhread.join
取消协作标志位/信号interrupt 协作中断
共享内存手动同步synchronized/Lock/volatile/JUC

对 C 程序员来说,Java 线程也是 OS 线程抽象,但 Java 提供了更完整的内存模型、同步关键字和并发工具库。


💡 最佳实践

  1. 优先使用 Runnable/Callable(避免单继承限制)
  2. 不要调用 stop()(使用标志位停止)
  3. 响应中断(在循环中检查 isInterrupted())
  4. 合理设置优先级(不要过度依赖)
  5. 注意线程安全(多线程访问共享数据要同步)

📝 练习

完成 练习/Ex23_Thread_Basics.java

  1. 三种方式创建线程
  2. 线程生命周期观察
  3. 线程方法应用
  4. 守护线程实践
  5. 线程安全问题演示
  6. 综合:多线程下载器

🎓 下一步

  • 第24课:线程同步 - synchronized、Lock、volatile