0
0
0

Java 多线程:线程的创建方式与生命周期

2026-11-02
2026-11-02
文章摘要
|

一、Java 创建线程的几种方式

Java 中常见的线程创建与执行方式有以下几种:

方式

特点

使用场景

继承 Thread

直接继承线程类

简单线程任务

实现 Runnable

将任务与线程分离

常见线程任务

Callable + FutureTask

可以获取任务返回值

需要获取执行结果

使用线程池

复用线程、统一管理

生产环境推荐

注意:前三种主要是定义和执行任务的不同方式,线程池则是线程管理与执行机制。

1. 继承 Thread 类

Thread 本身就是一个线程类,可以通过继承 Thread,然后重写 run() 方法来定义线程执行的任务。

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();
    }
}

缺点:

  1. Java 是单继承的,继承 Thread 后不能再继承其他类。

  2. 线程与业务任务耦合在一起。

  3. 不方便将任务交给其他线程执行。

因此,这种方式不是特别推荐。

2. 实现 Runnable 接口

Runnable 是一个函数式接口:

@FunctionalInterface
public interface Runnable {

    void run();

}

示例:

public class MyRunnable implements Runnable {

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

    public static void main(String[] args) {

        // 定义任务
        Runnable task = new MyRunnable();

        // 创建线程,将任务交给线程执行
        Thread thread = new Thread(task);

        thread.start();
    }
}

这里需要理解两个概念:

  • Runnable:定义要执行的任务。

  • Thread:负责启动线程并执行任务。

执行关系:

Runnable
   |
   | 定义任务
   v
Thread
   |
   | start()
   v
新线程执行 run()

Runnable 的优势:

  1. 任务与线程分离,降低耦合。

  2. 不占用 Java 类的继承关系。

  3. 一个 Runnable 任务可以交给不同线程执行。

  4. 可以方便地配合线程池使用。

Java 8 可以使用 Lambda 简化:

public class LambdaThreadDemo {

    public static void main(String[] args) {

        Runnable task = () -> {
            System.out.println(
                    Thread.currentThread().getName()
            );
        };

        new Thread(task).start();
    }
}

3. Callable + FutureTask

Runnable 有一个限制:run() 方法没有返回值,也不能直接通过方法签名声明抛出受检异常。

如果希望线程执行任务后返回结果,可以使用 Callable。

Callable 接口:

public interface Callable<V> {

    V call() throws Exception;

}

与 Runnable 的区别:

对比

Runnable

Callable

方法

run()

call()

返回值

void

有返回值

受检异常

不能声明抛出

可以声明抛出 Exception

获取执行结果

不直接支持

配合 Future 获取

示例:

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

public class CallableDemo {

    public static void main(String[] args) throws Exception {

        // 定义任务
        Callable<Integer> task = () -> {

            System.out.println("正在计算...");

            return 100 + 200;
        };

        // 使用 FutureTask 包装 Callable
        FutureTask<Integer> futureTask =
                new FutureTask<>(task);

        // 交给 Thread 执行
        Thread thread = new Thread(futureTask);

        thread.start();

        // 获取执行结果
        Integer result = futureTask.get();

        System.out.println("计算结果:" + result);
    }
}

执行流程:

Callable
   |
   | 定义任务,返回计算结果
   v
FutureTask
   |
   | 包装任务,管理执行状态和结果
   v
Thread
   |
   | start()
   v
执行 Callable.call()
   |
   v
FutureTask.get()
   |
   v
获取最终结果

这里有几个重要知识点:

第一:为什么 Callable 需要 FutureTask?

因为 Thread 的构造方法接收 Runnable,而 Callable 并不是 Runnable。

FutureTask 同时实现了 Runnable 和 Future,因此可以包装 Callable,再交给 Thread 执行。

第二:FutureTask.get() 有什么作用?

get() 用于获取任务的执行结果。

如果任务还没有完成,调用 get() 的线程就会阻塞,直到任务完成。

thread.start();

// 如果任务没有完成,这里会等待
Integer result = futureTask.get();

System.out.println("继续执行");

注意:阻塞的是调用 get() 的线程,不一定是 main 线程。

第三:FutureTask 除了获取结果,还有什么作用?

  • 管理任务执行状态。

  • 获取任务执行结果。

  • 获取任务执行异常。

  • 支持取消任务。

4. 使用线程池

生产环境中,不推荐每次需要执行任务时都手动创建一个 Thread。

因为线程创建和销毁存在开销,而且线程数量过多可能导致:

  • 内存占用增加。

  • CPU 上下文切换增加。

  • 系统资源耗尽。

  • 线程数量难以管理。

因此,生产环境通常使用线程池。

Java 8 示例:

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

public class ThreadPoolDemo {

    public static void main(String[] args) {

        ExecutorService executor =
                Executors.newFixedThreadPool(5);

        executor.execute(() -> {

            System.out.println(
                    "当前线程:" +
                    Thread.currentThread().getName()
            );

        });

        executor.shutdown();
    }
}

线程池可以复用已有线程,不需要每次执行任务都创建新线程。

生产环境注意:

Executors.newFixedThreadPool() 使用无界等待队列,任务持续堆积时可能造成内存问题。

生产环境更推荐使用 ThreadPoolExecutor,自定义核心线程数、最大线程数、任务队列和拒绝策略。

线程池的底层原理和参数配置将在后续课程专门学习。


二、start() 和 run() 的区别

这是 Java 多线程中非常经典的知识点。

先看代码:

public class StartRunDemo {

    public static void main(String[] args) {

        Thread thread = new Thread(() -> {

            System.out.println(
                    Thread.currentThread().getName()
            );

        });

        // 方式一
        thread.run();

        // 方式二
        thread.start();
    }
}

1. 调用 run()

thread.run();

这只是普通的方法调用,不会创建新线程。

任务会在当前调用线程中执行。

因此,第一次输出:

main

2. 调用 start()

thread.start();

这才是真正启动一个新线程。

JVM 启动新的执行线程,由新线程执行 run() 方法。

第二次输出:

Thread-0

3. 两者区别

对比

run()

start()

是否启动新线程

否

是

是否异步执行

否

是

执行线程

当前线程

新线程

是否可以调用多次

普通方法可以

一个 Thread 实例只能成功启动一次

重点:

  • run() 只是普通方法调用。

  • start() 才是真正启动线程。

  • 调用 start() 后,不保证线程立即获得 CPU 执行。

  • 一个 Thread 对象不能重复调用 start()。

例如:

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

thread.start();

thread.start(); // 抛出 IllegalThreadStateException

如果需要再次执行任务,需要创建一个新的 Thread 对象。


三、Java 线程的六种生命周期状态

Java 中,线程有六种状态,定义在:

java.lang.Thread.State

这是 Java 自带的,不需要引入第三方依赖。

六种状态:

状态

含义

NEW

新建状态

RUNNABLE

可运行状态

BLOCKED

阻塞状态

WAITING

无限期等待

TIMED_WAITING

限时等待

TERMINATED

终止状态

1. NEW:新建状态

创建 Thread 对象,但是还没有调用 start()。

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

System.out.println(thread.getState());

结果:

NEW

此时只是创建了线程对象,还没有真正启动线程。

2. RUNNABLE:可运行状态

调用 start() 之后,线程进入 RUNNABLE 状态。

thread.start();

这里需要特别注意:

Java 的 RUNNABLE 同时包含操作系统层面的就绪和运行状态。

也就是说:

  • 线程已经准备好,等待 CPU 调度。

  • 线程正在 CPU 上执行。

这两种情况在 Java 的 Thread.State 中都属于 RUNNABLE。

Java 没有单独定义 READY 和 RUNNING 两种状态。

3. BLOCKED:阻塞状态

线程等待获取 synchronized 锁时,会进入 BLOCKED 状态。

示例:

public class BlockedDemo {

    private static final Object LOCK = new Object();

    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {

            synchronized (LOCK) {

                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }

            }

        });

        Thread t2 = new Thread(() -> {

            synchronized (LOCK) {
                System.out.println("获取到锁");
            }

        });

        t1.start();

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        t2.start();
    }
}

执行过程:

  1. t1 获取 LOCK 锁。

  2. t1 调用 sleep(5000),进入 TIMED_WAITING。

  3. 但是 sleep() 不会释放 LOCK 锁。

  4. t2 尝试获取 LOCK 锁。

  5. 因为 LOCK 已经被 t1 持有,所以 t2 进入 BLOCKED 状态。

重点:BLOCKED 特指等待获取 synchronized 监视器锁的状态。

不要把所有线程等待情况都称为 BLOCKED。

4. WAITING:无限期等待

线程进入 WAITING 状态,表示它正在无限期等待其他线程执行某个特定操作。

常见方式:

object.wait();

thread.join();

LockSupport.park();

示例:

public class WaitingDemo {

    public static void main(String[] args) {

        Thread thread = new Thread(() -> {

            synchronized (WaitingDemo.class) {

                try {
                    WaitingDemo.class.wait();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }

            }

        });

        thread.start();
    }
}

线程调用 wait() 后进入 WAITING 状态。

需要其他线程调用对应对象的 notify()、notifyAll(),或者通过中断等方式结束等待。

这里有一个重要知识点:

wait() 会释放当前调用对象的监视器锁。

注意:wait() 必须在持有对应对象监视器锁的情况下调用,否则会抛出 IllegalMonitorStateException。

5. TIMED_WAITING:限时等待

TIMED_WAITING 和 WAITING 的区别:

TIMED_WAITING 有明确的等待时间。

常见方法:

Thread.sleep(1000);

object.wait(1000);

thread.join(1000);

LockSupport.parkNanos(1000000);

例如:

Thread thread = new Thread(() -> {

    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }

});

thread.start();

线程执行 sleep(3000) 后,会进入 TIMED_WAITING 状态。

等待时间结束后,线程可以重新进入 RUNNABLE 状态。

特别注意:

  • sleep() 不会释放已经持有的 synchronized 锁。

  • wait() 会释放调用对象的监视器锁。

6. TERMINATED:终止状态

线程执行完 run() 方法后,进入 TERMINATED 状态。

例如:

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

thread.start();

任务执行完毕,线程就会终止。

注意:

线程终止后不能重新启动。


四、线程生命周期状态转换

将上面的知识点串起来:

NEW
 |
 | start()
 v
RUNNABLE
 |
 | 等待 synchronized 锁
 v
BLOCKED
 |
 | 获取锁
 v
RUNNABLE

RUNNABLE
 |
 | wait() / join() / park()
 v
WAITING
 |
 | notify() / notifyAll() / interrupt() 等
 v
RUNNABLE

RUNNABLE
 |
 | sleep() / wait(timeout) / join(timeout)
 v
TIMED_WAITING
 |
 | 等待时间结束或被唤醒
 v
RUNNABLE

RUNNABLE
 |
 | run() 执行结束
 v
TERMINATED

注意:

  1. 这是一张简化的状态转换图。

  2. 线程可以在 RUNNABLE、BLOCKED、WAITING 和 TIMED_WAITING 之间多次切换。

  3. 被唤醒不代表立即获得 CPU 执行。

  4. 线程进入 TERMINATED 后不能重新启动。


五、容易混淆的知识点:sleep() 和 wait()

这两个方法都可以让线程暂停执行,但它们有本质区别。

对比

sleep()

wait()

所属类

Thread

Object

是否需要 synchronized

不需要

必须持有对应监视器锁

是否释放锁

不释放

释放调用对象的监视器锁

是否可以指定时间

可以

可以

唤醒方式

时间结束或中断

notify、notifyAll、中断或超时

主要用途

暂停当前线程

线程间协作

示例:sleep() 不释放锁

synchronized (LOCK) {

    Thread.sleep(3000);

}

线程虽然休眠了,但仍然持有 LOCK。

其他线程无法获取这把锁。

示例:wait() 释放锁

synchronized (LOCK) {

    LOCK.wait();

}

线程调用 wait() 后,会释放 LOCK,让其他线程有机会获取锁。

注意:wait() 被唤醒后,需要重新竞争并获得 LOCK,才能继续执行。


六、重点总结

  1. 继承 Thread、实现 Runnable、Callable + FutureTask 都是常见的线程执行方式。

  2. Runnable 用于定义任务,Thread 用于启动线程并执行任务。

  3. Callable 可以返回执行结果,并且可以声明抛出受检异常。

  4. FutureTask 可以包装 Callable,同时实现 Runnable 和 Future。

  5. FutureTask.get() 会阻塞调用线程,直到任务完成。

  6. 生产环境推荐使用线程池管理线程。

  7. start() 会启动新线程,run() 只是普通方法调用。

  8. 一个 Thread 对象只能成功调用一次 start()。

  9. Java 线程有六种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。

  10. RUNNABLE 包含操作系统层面的就绪和运行状态。

  11. BLOCKED 特指等待获取 synchronized 监视器锁。

  12. sleep() 不释放已经持有的锁,wait() 会释放调用对象的监视器锁。

  13. wait() 必须在持有对应对象监视器锁的情况下调用。

七、常见问题与答案

1. Runnable 和 Thread 有什么区别?

Runnable 是任务接口,负责定义要执行的任务;Thread 是线程类,负责启动线程并执行任务。

将任务与线程分离,更符合面向对象设计原则。

2. Callable 为什么需要 FutureTask?

因为 Thread 构造方法接收 Runnable,而 Callable 不是 Runnable。

FutureTask 同时实现 Runnable 和 Future,因此可以包装 Callable 并交给 Thread 执行,还能获取返回结果。

3. start() 调用后线程一定立即执行吗?

不一定。调用 start() 后,线程进入 RUNNABLE 状态,具体什么时候获得 CPU 执行,由线程调度机制决定。

4. 线程进入 BLOCKED 和 WAITING 有什么区别?

BLOCKED 是等待获取 synchronized 锁;WAITING 是线程主动等待其他线程完成某个操作,例如 wait()、join()。

5. sleep() 和 wait() 有什么区别?

sleep() 是 Thread 的静态方法,不释放已经持有的锁;wait() 是 Object 的方法,会释放调用对象的监视器锁。

6. 线程执行完毕后还能再次 start() 吗?

不能。一个 Thread 对象只能启动一次,再次调用 start() 会抛出 IllegalThreadStateException。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或者给予支持!

评论