CodeGym /课程 /JAVA 25 SELF /线程启动:Thread 与 Runnable,语法

线程启动:Thread 与 Runnable,语法

JAVA 25 SELF
第 51 级 , 课程 1
可用

1. Thread 类:在 Java 中的第一个线程

在 Java 中,每个执行线程都由 Thread 类的对象表示。这个类就像乐队的指挥:负责管理线程的启动、停止和生命周期。

要启动一个线程,需要:

  1. 创建一个 Thread(或其子类)的对象。
  2. 调用该对象的 start() 方法。

让我们通过实践来理解。

示例 1. 继承 Thread

最直接的创建线程方式是:让你的类继承 Thread,并重写它的 run() 方法。你在 run() 中写的内容会在单独的线程中执行。

// 通过继承的简单线程
public class HelloThread extends Thread {
    @Override
    public void run() {
        System.out.println("来自线程的问候!我的名字是:" + getName());
    }
}

public class Main {
    public static void main(String[] args) {
        HelloThread thread = new HelloThread(); // 创建线程对象
        thread.start(); // 启动线程
        System.out.println("主线程结束运行。");
    }
}

实际上发生了什么?
当你调用 thread.start() 时,Java 会创建一个新线程,并在该新线程中启动 run() 方法。同时,主线程(从 main 开始的那个)不会等待,而是并行继续执行。

重要提醒:不要把 start() 和直接调用 run() 混淆。如果写成 thread.run(),不会创建新线程——这只是一个普通方法,会在当前线程中执行。真正的多线程只会从 start() 开始。

什么是 getName()?

getName() 方法会返回线程的名称。默认情况下,Java 会给线程起名为 "Thread-0""Thread-1" 等等。这对于调试很有帮助。

2. Runnable 接口:大多数情况下的更佳方式

Java 追求灵活性。Thread 类已经继承自 Object,而 Java 不支持类的多重继承。如果你的类继承了 Thread,就不能再继承其他类(比如你有一个 Car 类,想把它变成线程,就必须二选一)。

解决方案:把线程逻辑放到一个实现了 Runnable 接口的独立对象中。该接口只有一个方法 run()。然后创建一个 Thread 对象,把你的 Runnable 传给它——再启动线程。

示例 2. 带 Runnable 的类

// 实现 Runnable 的类
public class HelloRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("来自 Runnable 的问候!线程:" + Thread.currentThread().getName());
    }
}

public class Main {
    public static void main(String[] args) {
        Runnable runnable = new HelloRunnable();
        Thread thread = new Thread(runnable); // 将 Runnable 传入 Thread
        thread.start();
        System.out.println("主线程结束运行。");
    }
}

这里我们创建了一个实现 Runnable 接口的类 HelloRunnable。在 main 方法中创建该类的对象,并把它传给 Thread 的构造器。之后通过调用 start() 来启动线程。

值得单独一提的是 Thread.currentThread() 方法。它是一个静态方法,用来获取当前线程对象,从而知道我们的代码究竟在哪个线程中执行。

为什么这样更好?

  • 你的类可以继承任何其他类(而不只是 Thread)。
  • 可以复用同一个 Runnable,在多个线程中运行。
  • 代码更灵活、更清晰。

3. 语法:匿名类与 lambda 表达式

Java 在不断进化!自 Java 8 之后,我们可以写得更简洁、更方便。

示例 3. 匿名类

有时你不想为只用一次的类创建单独的文件。可以在使用处直接声明——这就是匿名类。

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名 Runnable!线程:" + Thread.currentThread().getName());
            }
        });
        thread.start();
        System.out.println("主线程结束运行。");
    }
}

示例 4. Lambda 表达式 (Java 8+)

Runnable 接口是函数式接口(只有一个抽象方法)。因此可以使用 lambda 表达式:

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("Lambda 线程!线程:" + Thread.currentThread().getName());
        });
        thread.start();
        System.out.println("主线程结束运行。");
    }
}

小结:
Runnable 可以“就地”实现,而 lambda 表达式提供了 run() 方法的实现。

4. 启动多个线程

你可以启动任意多个线程(几乎——这取决于计算机的资源)。

示例 5. 启动多个线程

public class Main {
    public static void main(String[] args) {
        for (int i = 1; i <= 3; i++) {
            int threadNumber = i; // 对于 lambda,必须复制变量!
            Thread thread = new Thread(() -> {
                System.out.println("线程 #" + threadNumber + ":你好!");
            });
            thread.start();
        }
        System.out.println("主线程结束运行。");
    }
}

输出可能不同!
线程并行运行——谁先输出取决于操作系统和线程调度器。

5. 线程的生命周期

理解线程如何运行,有助于调试和正确使用线程。

线程的主要状态

  • NEW — 线程已创建,但尚未启动(new Thread(...))。
  • RUNNABLE — 线程已启动(start()),可能正在执行。
  • TERMINATED — 线程已结束(run() 方法执行完毕)。

调用 start() 与 run() 时发生了什么

  • start() — 创建一个新线程,并在该新线程中调用它的 run() 方法。
  • run() — 普通方法;如果直接调用,它会在当前线程中执行(不会创建新线程)。

示例:

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> System.out.println("来自线程的 run():" + Thread.currentThread().getName()));
        thread.run();   // 在主线程中调用!
        thread.start(); // 在单独的线程中调用!
    }
}

6. 实践:做一个简单的多线程应用

让我们继续完善我们的教学示例——假设我们在编写一个简单的日志系统,每个线程写出自己的消息。

public class LoggerTask implements Runnable {
    private final String message;

    public LoggerTask(String message) {
        this.message = message;
    }

    @Override
    public void run() {
        System.out.println("[" + Thread.currentThread().getName() + "] 日志:" + message);
    }
}

public class Main {
    public static void main(String[] args) {
        Thread logger1 = new Thread(new LoggerTask("系统启动"));
        Thread logger2 = new Thread(new LoggerTask("加载数据"));
        Thread logger3 = new Thread(new LoggerTask("处理请求"));

        logger1.start();
        logger2.start();
        logger3.start();

        System.out.println("主线程结束运行。");
    }
}

发生了什么?
三个线程并行地向控制台写出各自的消息。消息的顺序不保证——这正是多线程的特性。

7. 一些有用的细节

表格:创建线程方式的比较

方式 灵活性 可复用性 是否推荐?
继承 Thread 仅用于简单/教学示例
实现 Runnable
匿名类 中等 用于一次性的任务
Lambda 表达式 是(Java 8+)

示意图:线程启动如何工作

+----------------------+ 
|  main (主线程)       |
+----------------------+
           |
           v
+----------------------+
|  Thread thread = ... |
+----------------------+
           |
           v
+----------------------+
|  thread.start()      |  ← 线程“开始运行”
+----------------------+
           |
           v
+----------------------+
|  run() 在新线程中    |
+----------------------+
           |
           v
+----------------------+
|  线程已结束          |
+----------------------+

8. 启动线程的常见错误

错误 1:调用 run() 而不是 start()
新手非常常见的错误是直接调用线程对象的 run() 方法。此时不会创建新线程,方法只会在当前(主)线程中执行。正确做法:启动线程时始终使用 start()

错误 2:重复启动同一个线程。
不能对同一个 Thread 对象多次调用 start()——这会抛出 IllegalThreadStateException。如果需要再次运行任务,请创建一个新的 Thread 对象。

错误 3:未同步就修改共享变量。
如果多个线程访问同一组变量,结果可能出乎意料(竞态条件)。关于同步我们会稍后讨论,但目前请务必小心。

错误 4:没有等待线程结束。
如果必须等待某个线程完成,请使用 join()。否则主线程可能会先于子线程结束。

错误 5:把线程名和类名混淆。
getName() 方法返回的是线程名,而不是类名。为了调试,最好通过构造器或 setName() 显式设置线程名。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION