一、线程的状态及流转

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

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

可以这样理解:


1. NEW:新建状态

当你只是创建了线程对象,但还没有调用 start() 方法时,线程处于 NEW 状态

Thread t = new Thread();

此时只是 JVM 中有了一个线程对象,真正的操作系统线程还没有启动。


2. RUNNABLE:可运行状态

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

t.start();

注意,Java 中没有单独区分“就绪”和“运行”。

在操作系统层面,线程可能有两种情况:

就绪:等待 CPU 调度
运行:正在 CPU 上执行

但是在 Java 的 Thread.State 中,这两种情况都统一叫:

RUNNABLE

所以可以理解为:

调用 start()
NEW --------------> RUNNABLE

抢到 CPU 执行权
RUNNABLE ---------> 正在运行

CPU 时间片用完
正在运行 ---------> RUNNABLE

但是 Java 代码里查看状态时,看到的还是 RUNNABLE


3. BLOCKED:阻塞状态

当线程想进入 synchronized 修饰的代码块或方法,但是锁被其他线程持有时,就会进入 BLOCKED 状态。

例如:

synchronized (lock) {
    // 临界区代码
}

假设 A 线程已经拿到了 lock 锁,B 线程也想进入这段代码,那么 B 线程就会进入:

BLOCKED

等 A 线程释放锁之后,B 线程才有机会重新进入 RUNNABLE 状态。

重点:

BLOCKED 是等待 synchronized 锁。

4. WAITING:无限等待状态

线程调用某些方法后,会进入无限等待状态,直到被其他线程唤醒。

常见方法有:

Object.wait();
Thread.join();
LockSupport.park();

比如:

lock.wait();

线程会释放锁,并进入 WAITING 状态,直到其他线程调用:

lock.notify();
lock.notifyAll();

再比如:

bThread.join();

A 线程调用 bThread.join() 后,会等待 B 线程执行完毕。此时 A 线程也会进入等待状态。

重点:

WAITING 是无限等待,必须等别人唤醒或等待的线程结束。

5. TIMED_WAITING:计时等待状态

线程调用带有时间参数的方法时,会进入 TIMED_WAITING 状态。

常见方法:

Thread.sleep(1000);
Object.wait(1000);
Thread.join(1000);
LockSupport.parkNanos();
LockSupport.parkUntil();

比如:

Thread.sleep(2000);

线程会睡眠 2 秒,2 秒后自动回到 RUNNABLE 状态。

重点:

TIMED_WAITING 是有限时间等待,时间到了可以自动恢复。

6. TERMINATED:终止状态

当线程的 run() 方法执行完毕,或者执行过程中抛出未处理异常,线程就会进入终止状态。

public void run() {
    System.out.println("执行任务");
}

执行完之后:

RUNNABLE -> TERMINATED

线程结束后,不能再次调用 start()

如果再次调用:

t.start();
t.start();

会报错:

IllegalThreadStateException

二、线程状态流转总结

可以这样记:

创建线程对象
NEW

调用 start()
NEW -> RUNNABLE

抢到 CPU
RUNNABLE -> 运行中
但是 Java 中仍然显示为 RUNNABLE

等待 synchronized 锁
RUNNABLE -> BLOCKED

调用 wait/join/park
RUNNABLE -> WAITING

调用 sleep/wait(time)/join(time)
RUNNABLE -> TIMED_WAITING

run 方法执行结束
RUNNABLE -> TERMINATED

面试中可以这样回答:

Java 线程在 Thread.State 中定义了六种状态,分别是 NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。线程创建后是 NEW,调用 start 后进入 RUNNABLE。Java 中的 RUNNABLE 包含操作系统层面的就绪和运行。线程竞争 synchronized 锁失败会进入 BLOCKED,调用 wait、join 等方法会进入 WAITING,调用 sleep 或带时间参数的 wait、join 会进入 TIMED_WAITING,run 方法执行结束后进入 TERMINATED。

三、创建线程的方式

方式一:继承 Thread 类

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程执行");
    }
}

public class Demo {
    public static void main(String[] args) {
        new MyThread().start();
    }
}

特点:

简单直接,但是 Java 是单继承,继承 Thread 后就不能继承其他类。

方式二:实现 Runnable 接口

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程执行");
    }
}

public class Demo {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start();
    }
}

特点:

更推荐,任务和线程分离,避免单继承限制。

也可以用 Lambda:

new Thread(() -> {
    System.out.println("线程执行");
}).start();

方式三:实现 Callable 接口结合 FutureTask

Runnable 没有返回值,也不能直接抛出受检异常。

如果任务需要返回结果,可以使用 Callable

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

public class Demo {
    public static void main(String[] args) throws Exception {
        Callable<Integer> callable = () -> {
            return 100;
        };

        FutureTask<Integer> futureTask = new FutureTask<>(callable);

        Thread t = new Thread(futureTask);
        t.start();

        Integer result = futureTask.get();
        System.out.println(result);
    }
}

特点:

适合需要返回值的异步任务。
futureTask.get() 会阻塞当前线程,直到任务执行完并返回结果。

方式四:通过线程池创建线程

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

public class Demo {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(3);

        pool.execute(() -> {
            System.out.println("线程池执行任务");
        });

        pool.shutdown();
    }
}

特点:

实际开发中更推荐使用线程池。

因为线程池可以:

复用线程
减少频繁创建和销毁线程的开销
统一管理线程资源
提高系统稳定性

四、多线程的应用场景

多线程适合处理一些耗时任务,避免主线程被长时间阻塞。

比如:

文件拷贝
文件上传下载
加载大量资源
网络请求
聊天软件收发消息
后台服务器处理多个请求
定时任务
日志异步写入
消息队列消费

举个例子:

如果一个服务器只有一个线程,那么同时来了 100 个请求,只能一个一个处理。

使用多线程后,可以多个请求同时被处理,提高并发能力。


五、A 线程启动 B 线程,A 要等 B 执行完再继续,怎么实现?

这个问题的核心是:

线程之间的协作 / 等待另一个线程执行完成

常见方法有三种:

Thread.join()
CountDownLatch
CompletableFuture

方法一:Thread.join()

代码

public class JoinDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread bThread = new Thread(() -> {
            System.out.println("B 线程开始执行");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("B 线程执行完毕");
        });

        bThread.start();

        bThread.join();

        System.out.println("A 线程继续执行");
    }
}

原理

A 线程调用:

bThread.join();

意思是:

A 等待 bThread 执行结束。

所以执行流程是:

A 线程启动
A 创建 B 线程
A 调用 bThread.start()
B 开始执行
A 调用 bThread.join(),A 阻塞等待
B 执行完毕
A 从 join() 返回
A 继续执行

执行顺序:

B 线程开始执行
B 线程执行完毕
A 线程继续执行

注意:

bThread.join();

阻塞的是调用 join 的线程,也就是 A 线程,不是 B 线程。


方法二:CountDownLatch

CountDownLatch 可以理解为一个倒计时计数器。

代码

import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(1);

        Thread bThread = new Thread(() -> {
            System.out.println("B 线程开始执行");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("B 线程执行完毕");

            latch.countDown();
        });

        bThread.start();

        latch.await();

        System.out.println("A 线程继续执行");
    }
}

原理

CountDownLatch latch = new CountDownLatch(1);

表示需要等待 1 个任务完成。

A 线程调用:

latch.await();

意思是:

只要计数器不为 0,A 就一直等。

B 线程执行完之后调用:

latch.countDown();

意思是:

计数器减 1。

计数器从 1 变成 0 后,A 线程就会被唤醒,然后继续执行。

流程:

A 创建 CountDownLatch,计数器为 1
A 启动 B 线程
A 调用 await(),等待计数器变成 0
B 执行任务
B 执行完调用 countDown()
计数器变成 0
A 被唤醒
A 继续执行

CountDownLatch 适合什么场景?

join() 适合等待某一个具体线程。

CountDownLatch 更适合等待多个任务完成。

例如 A 线程要等 B、C、D 三个线程都执行完:

CountDownLatch latch = new CountDownLatch(3);

每个线程执行完:

latch.countDown();

A 线程:

latch.await();

只有计数器变成 0,A 才继续。


方法三:CompletableFuture

CompletableFuture 是 Java 8 引入的异步编程工具。

代码

import java.util.concurrent.CompletableFuture;

public class CompletableFutureDemo {
    public static void main(String[] args) {
        System.out.println("A 线程启动 B 线程");

        CompletableFuture.runAsync(() -> {
            System.out.println("B 线程执行");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("B 线程执行完毕");
        }).join();

        System.out.println("A 线程继续执行");
    }
}

原理

CompletableFuture.runAsync(() -> {
    // B 线程执行的任务
})

这行代码会把任务提交到默认线程池中异步执行。

默认使用的是:

ForkJoinPool.commonPool()

也就是说,B 任务不是由 A 线程执行,而是由线程池里的其他线程执行。

然后:

.join();

表示:

A 线程等待这个异步任务执行完成。

所以流程是:

A 线程启动
A 提交异步任务
线程池中的某个线程执行 B 任务
A 调用 join() 等待异步任务完成
B 任务执行完毕
A 继续执行

CompletableFuture 中的 join 和 get 区别

CompletableFuture 也可以用:

future.get();

例如:

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    System.out.println("B 线程执行");
});

future.get();

区别是:

get() 需要处理受检异常:InterruptedException、ExecutionException
join() 不需要显式处理受检异常,会把异常包装成 CompletionException

所以:

future.join();

写起来更简单。

六、三种方式怎么选?

可以这样总结:

方法 适合场景 特点
Thread.join() A 等待一个具体线程 B 执行完 简单直接
CountDownLatch A 等待一个或多个线程/任务完成 适合多线程协作
CompletableFuture 异步任务编排 适合链式异步任务、组合任务、有返回值任务

面试回答可以这样说

如果 A 线程启动 B 线程后,需要等待 B 线程执行完再继续,可以使用 Thread.join()。A 线程调用 bThread.join() 后会阻塞,直到 B 线程执行结束。也可以使用 CountDownLatch,把计数器初始化为 1,A 调用 await() 等待,B 执行完后调用 countDown(),计数器归零后 A 继续执行。如果是异步编程场景,也可以使用 CompletableFuture,比如 CompletableFuture.runAsync() 启动异步任务,然后调用 join() 等待任务完成。

重点区别

你可以重点记住这句话:

join 是等一个具体线程结束;
CountDownLatch 是等一个或多个任务完成;
CompletableFuture 是异步任务编排工具,可以等待任务完成,也可以处理返回值和后续任务。

另外注意:

sleep 不会释放锁;
wait 会释放锁;
join 本质上是当前线程等待目标线程结束;
CountDownLatch 的 await 会阻塞当前线程;
CompletableFuture 的 join 会阻塞当前线程等待异步任务完成。

七、一句话区别wait和await

wait 是 Object 的方法,配合 synchronized 使用;
await 是并发工具类的方法,常见于 CountDownLatch、Condition 等。

1. wait 是什么?

wait()Object 类的方法。

也就是说,任何对象都可以调用:

lock.wait();

但它必须在 synchronized 代码块或同步方法中使用。

例如:

synchronized (lock) {
    lock.wait();
}

否则会抛异常:

IllegalMonitorStateException

2. wait 的特点

调用 wait() 后:

当前线程会释放锁
当前线程进入 WAITING 状态
需要其他线程调用 notify() 或 notifyAll() 唤醒

例子:

Object lock = new Object();

Thread t1 = new Thread(() -> {
    synchronized (lock) {
        try {
            System.out.println("t1 开始等待");
            lock.wait();
            System.out.println("t1 被唤醒");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});

Thread t2 = new Thread(() -> {
    synchronized (lock) {
        System.out.println("t2 唤醒 t1");
        lock.notify();
    }
});

t1 调用 wait() 后会释放 lock,然后等待 t2 调用 notify()

3. await 是什么?

await() 不是 Object 的方法,它通常出现在 JUC 并发工具类中。

常见的有两类:

CountDownLatch.await();
Condition.await();
CyclicBarrier.await();

它们虽然都叫 await,但含义不完全一样。


4. CountDownLatch.await()

你前面代码里的 await() 是这个:

latch.await();

它的作用是:

当前线程等待,直到 CountDownLatch 的计数器变成 0。

例如:

CountDownLatch latch = new CountDownLatch(1);

Thread b = new Thread(() -> {
    System.out.println("B 线程执行任务");
    latch.countDown();
});

b.start();

latch.await();

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

流程是:

A 调用 latch.await()
A 阻塞等待

B 执行完任务
B 调用 latch.countDown()
计数器从 1 变成 0

A 被唤醒,继续执行

这里的重点是:

CountDownLatch.await() 等的是计数器归零。

它不需要配合 synchronized 使用。


5. Condition.await()

还有一种 await()Condition 的方法。

它和 wait() 比较像。

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

lock.lock();
try {
    condition.await();
} finally {
    lock.unlock();
}

它必须在获取 Lock 之后调用。

对应关系可以这样看:

synchronized 体系 Lock 体系
synchronized ReentrantLock
wait() Condition.await()
notify() Condition.signal()
notifyAll() Condition.signalAll()

所以:

lock.wait();

对应的是:

condition.await();

而不是 CountDownLatch.await()


6. 核心区别

对比点 wait() CountDownLatch.await() Condition.await()
所属类 Object CountDownLatch Condition
使用前提 必须在 synchronized 不需要 synchronized 必须先获取 Lock
是否释放锁 会释放 synchronized 不涉及锁释放 会释放 Lock
唤醒方式 notify() / notifyAll() countDown() 使计数器归零 signal() / signalAll()
主要用途 线程通信 等待多个任务完成 Lock 体系下的线程通信
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐