跳转至

线程基础 (Thread Fundamentals)

理解线程的概念、创建方式、生命周期,掌握多线程编程的基础知识

目录


1. 什么是线程 (What is Thread)

1.1 进程与线程

进程(Process):操作系统进行资源分配和调度的基本单位,每个进程都有独立的内存空间。

线程(Thread):进程内的执行单元,是CPU调度的基本单位。一个进程可以包含多个线程,这些线程共享进程的内存空间。

进程 (Process)
├── 内存空间(堆、方法区)
├── 文件描述符
├── 线程1 (Thread 1)
├── 线程2 (Thread 2)
└── 线程3 (Thread 3)

1.2 为什么需要多线程?

  1. 提高CPU利用率 - 在IO等待时,CPU可以执行其他线程
  2. 提高响应速度 - 后台任务不阻塞用户界面
  3. 充分利用多核CPU - 并行处理提高吞吐量
  4. 简化编程模型 - 某些场景下多线程更直观

1.3 在算力平台中的应用

在算力平台中,多线程的应用场景:

  • 任务调度线程 - 定时从Nomad获取任务状态
  • 节点监控线程 - 并发采集多个节点的资源信息
  • 结算处理线程 - 批量处理用户钱包的扣费和充值
  • Web请求处理 - Tomcat使用线程池处理HTTP请求

2. 线程的创建方式 (Thread Creation)

2.1 方式一:继承Thread类

/**
 * 方式一:继承Thread类
 * Method 1: Extend Thread Class
 * 
 * 优点:简单直接
 * 缺点:Java单继承,不够灵活
 */
public class MyThread extends Thread {

    @Override
    public void run() {
        // 线程执行的代码
        System.out.println("线程执行中: " + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start(); // 启动线程,调用run()方法
    }
}

注意事项: - 必须重写run()方法 - 调用start()方法启动线程,而不是直接调用run() - start()会创建新线程,run()只是普通方法调用

2.2 方式二:实现Runnable接口(推荐)

/**
 * 方式二:实现Runnable接口(推荐)
 * Method 2: Implement Runnable Interface (Recommended)
 * 
 * 优点:可以继承其他类,更灵活
 * 缺点:无返回值,不能抛出受检异常
 */
public class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println("线程执行中: " + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        // 方式1:创建Thread对象,传入Runnable
        Thread thread = new Thread(new MyRunnable());
        thread.start();

        // 方式2:使用Lambda表达式(Java 8+)
        Thread thread2 = new Thread(() -> {
            System.out.println("Lambda线程执行");
        });
        thread2.start();
    }
}

为什么推荐Runnable? 1. 可以继承其他类,更灵活 2. 符合面向接口编程的原则 3. 可以复用同一个Runnable对象创建多个线程

2.3 方式三:实现Callable接口

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/**
 * 方式三:实现Callable接口
 * Method 3: Implement Callable Interface
 * 
 * 优点:有返回值,可以抛出异常
 * 缺点:使用相对复杂
 */
public class MyCallable implements Callable<String> {

    @Override
    public String call() throws Exception {
        // 执行任务并返回结果
        Thread.sleep(1000);
        return "任务执行完成";
    }

    public static void main(String[] args) throws Exception {
        // Callable需要包装在FutureTask中
        FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
        Thread thread = new Thread(futureTask);
        thread.start();

        // 获取返回值(会阻塞直到任务完成)
        String result = futureTask.get();
        System.out.println("结果: " + result);
    }
}

Callable vs Runnable:

特性 Runnable Callable
返回值
异常 不能抛出受检异常 可以抛出异常
使用场景 简单任务 需要返回值的任务

2.4 方式四:使用线程池(生产环境推荐)

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 方式四:使用线程池(生产环境推荐)
 * Method 4: Use Thread Pool (Recommended for Production)
 * 
 * 优点:资源可控,性能好,便于管理
 */
public class ThreadPoolDemo {

    public static void main(String[] args) {
        // 创建线程池
        ExecutorService executor = Executors.newFixedThreadPool(5);

        // 提交任务
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executor.submit(() -> {
                System.out.println("任务 " + taskId + " 执行中");
            });
        }

        // 关闭线程池
        executor.shutdown();
    }
}

线程池的优势: 1. 资源复用 - 避免频繁创建和销毁线程 2. 资源可控 - 限制线程数量,防止资源耗尽 3. 任务管理 - 支持任务队列、拒绝策略等

详细内容请参考:07-05 - 线程池


3. 线程的生命周期 (Thread Lifecycle)

3.1 线程状态(Thread States)

Java线程有6种状态,定义在Thread.State枚举中:

public enum State {
    NEW,              // 新建状态
    RUNNABLE,         // 可运行状态
    BLOCKED,          // 阻塞状态
    WAITING,          // 等待状态
    TIMED_WAITING,    // 定时等待状态
    TERMINATED        // 终止状态
}

3.2 状态转换图

        NEW
         |
         | start()
    RUNNABLE ←────────┐
         |            |
         | 获取CPU时间片  |
         |            |
         |            |
    BLOCKED (等待锁)   |
         |            |
         | 获取到锁     |
         |            |
    WAITING (wait())  |
         |            |
         | notify()   |
         |            |
TIMED_WAITING (sleep)│
         |            │
         | 时间到/中断   │
         └────────────┘
         |
         | run()方法执行完毕
    TERMINATED

3.3 各状态详解

NEW(新建状态)

线程对象已创建,但尚未调用start()方法。

Thread thread = new Thread(() -> {});
System.out.println(thread.getState()); // NEW

RUNNABLE(可运行状态)

线程调用了start()方法,可能正在运行,也可能在等待CPU时间片。

Thread thread = new Thread(() -> {
    while (true) {
        // 执行任务
    }
});
thread.start();
System.out.println(thread.getState()); // RUNNABLE

注意: RUNNABLE状态包括: - Running - 正在CPU上执行 - Ready - 等待CPU时间片

BLOCKED(阻塞状态)

线程等待获取监视器锁(monitor lock),进入synchronized代码块时。

Object lock = new Object();

Thread thread1 = new Thread(() -> {
    synchronized (lock) {
        try {
            Thread.sleep(5000); // 持有锁5秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});

Thread thread2 = new Thread(() -> {
    synchronized (lock) { // 等待thread1释放锁
        System.out.println("获取到锁");
    }
});

thread1.start();
Thread.sleep(100); // 确保thread1先获取锁
thread2.start();
Thread.sleep(100);
System.out.println(thread2.getState()); // BLOCKED

WAITING(等待状态)

线程无限期等待其他线程的特定操作。

Object lock = new Object();

Thread thread = new Thread(() -> {
    synchronized (lock) {
        try {
            lock.wait(); // 进入WAITING状态
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});

thread.start();
Thread.sleep(100);
System.out.println(thread.getState()); // WAITING

进入WAITING的方法: - Object.wait() - 等待notify() - Thread.join() - 等待其他线程结束 - LockSupport.park() - 等待unpark()

TIMED_WAITING(定时等待状态)

线程等待指定的时间。

Thread thread = new Thread(() -> {
    try {
        Thread.sleep(5000); // 睡眠5秒
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});

thread.start();
Thread.sleep(100);
System.out.println(thread.getState()); // TIMED_WAITING

进入TIMED_WAITING的方法: - Thread.sleep(long) - 睡眠指定时间 - Object.wait(long) - 等待指定时间 - Thread.join(long) - 等待其他线程指定时间 - LockSupport.parkNanos() / parkUntil() - 定时等待

TERMINATED(终止状态)

线程执行完毕或异常退出。

Thread thread = new Thread(() -> {
    System.out.println("执行完毕");
});

thread.start();
thread.join(); // 等待线程结束
System.out.println(thread.getState()); // TERMINATED

4. 线程的基本操作 (Thread Basic Operations)

4.1 启动线程 - start()

Thread thread = new Thread(() -> {
    System.out.println("线程执行");
});
thread.start(); // 启动线程

start() vs run(): - start() - 创建新线程,异步执行 - run() - 普通方法调用,同步执行

4.2 等待线程结束 - join()

Thread thread = new Thread(() -> {
    try {
        Thread.sleep(2000);
        System.out.println("任务完成");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});

thread.start();
thread.join(); // 等待thread执行完毕
System.out.println("主线程继续执行");

join()的重载方法: - join() - 无限期等待 - join(long millis) - 等待指定毫秒数 - join(long millis, int nanos) - 等待指定时间(纳秒精度)

4.3 线程睡眠 - sleep()

try {
    Thread.sleep(1000); // 睡眠1秒
} catch (InterruptedException e) {
    // 处理中断异常
    e.printStackTrace();
}

注意事项: - sleep()不会释放锁 - 可能抛出InterruptedException - 睡眠时间不精确,可能更长

4.4 线程中断 - interrupt()

Thread thread = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        // 执行任务
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // 捕获中断异常,退出循环
            System.out.println("线程被中断");
            break;
        }
    }
});

thread.start();
Thread.sleep(5000);
thread.interrupt(); // 中断线程

中断机制: - interrupt() - 设置中断标志 - isInterrupted() - 检查中断标志(不清除) - interrupted() - 检查并清除中断标志

4.5 线程让步 - yield()

Thread thread = new Thread(() -> {
    for (int i = 0; i < 100; i++) {
        if (i % 10 == 0) {
            Thread.yield(); // 让出CPU时间片
        }
        System.out.println(i);
    }
});

注意: yield()只是建议,不保证一定让出CPU。


5. 守护线程 (Daemon Thread)

5.1 什么是守护线程?

守护线程(Daemon Thread):为其他线程提供服务的线程,当所有非守护线程结束时,守护线程会自动结束。

5.2 守护线程的特点

Thread daemonThread = new Thread(() -> {
    while (true) {
        System.out.println("守护线程运行中");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});

daemonThread.setDaemon(true); // 设置为守护线程
daemonThread.start();

// 主线程执行完毕后,守护线程自动结束
Thread.sleep(3000);
System.out.println("主线程结束");

守护线程的特点: 1. 必须在start()之前设置 2. 所有非守护线程结束时,守护线程自动结束 3. 不能用于执行重要任务(可能被强制结束)

5.3 应用场景

  • GC线程 - 垃圾回收
  • 监控线程 - 系统监控、日志记录
  • 定时任务线程 - 定时清理缓存

6. 线程优先级 (Thread Priority)

6.1 优先级设置

Thread thread1 = new Thread(() -> {
    for (int i = 0; i < 100; i++) {
        System.out.println("Thread 1: " + i);
    }
});

Thread thread2 = new Thread(() -> {
    for (int i = 0; i < 100; i++) {
        System.out.println("Thread 2: " + i);
    }
});

thread1.setPriority(Thread.MAX_PRIORITY); // 10
thread2.setPriority(Thread.MIN_PRIORITY); // 1

thread1.start();
thread2.start();

优先级范围: - MIN_PRIORITY = 1 - NORM_PRIORITY = 5(默认) - MAX_PRIORITY = 10

6.2 注意事项

⚠️ 优先级只是建议,不保证执行顺序

不同操作系统的线程调度策略不同,优先级可能不起作用。生产环境中**不建议依赖优先级**,应该使用更可靠的同步机制。


7. 最佳实践 (Best Practices)

7.1 线程命名

Thread thread = new Thread(() -> {
    // 任务逻辑
}, "Task-Thread-1"); // 给线程命名

// 或者在run()方法中
Thread.currentThread().setName("Custom-Name");

好处: 便于调试和日志追踪

7.2 异常处理

Thread thread = new Thread(() -> {
    try {
        // 可能抛出异常的任务
        riskyTask();
    } catch (Exception e) {
        // 记录日志
        logger.error("线程执行异常", e);
        // 不要吞掉异常
    }
});

// 或者使用UncaughtExceptionHandler
thread.setUncaughtExceptionHandler((t, e) -> {
    logger.error("线程 " + t.getName() + " 未捕获异常", e);
});

7.3 避免直接继承Thread

// ❌ 不推荐
class MyThread extends Thread {
    // ...
}

// ✅ 推荐
class MyTask implements Runnable {
    // ...
}
Thread thread = new Thread(new MyTask());

7.4 使用线程池

// ❌ 不推荐:直接创建线程
for (int i = 0; i < 100; i++) {
    new Thread(() -> {
        // 任务
    }).start();
}

// ✅ 推荐:使用线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
    executor.submit(() -> {
        // 任务
    });
}

8. 面试高频问题 (Interview Questions)

Q1: start()和run()的区别?

答案: - start() - 创建新线程,异步执行run()方法 - run() - 普通方法调用,同步执行,不会创建新线程

Thread thread = new Thread(() -> System.out.println("执行"));

thread.start(); // 创建新线程执行
thread.run();   // 在当前线程执行

Q2: 线程的生命周期有哪些状态?

答案: NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED

Q3: 如何创建线程?推荐哪种方式?

答案: 1. 继承Thread类 2. 实现Runnable接口(推荐) 3. 实现Callable接口(需要返回值时) 4. 使用线程池(生产环境推荐)

Q4: 守护线程和普通线程的区别?

答案: - 守护线程:为其他线程服务,所有非守护线程结束时自动结束 - 普通线程:执行主要业务逻辑,必须执行完毕

Q5: sleep()和wait()的区别?

答案:

特性 sleep() wait()
所属类 Thread Object
释放锁
唤醒方式 时间到 notify()/notifyAll()
使用场景 暂停执行 线程间通信

📖 扩展阅读


返回: 07-Java并发编程
下一章: 07-02 - 线程同步与内存模型 →


上一章: ← 06 - Java集合框架