Java 多线程:线程的创建方式与生命周期
一、Java 创建线程的几种方式
Java 中常见的线程创建与执行方式有以下几种:
注意:前三种主要是定义和执行任务的不同方式,线程池则是线程管理与执行机制。
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();
}
}缺点:
Java 是单继承的,继承 Thread 后不能再继承其他类。
线程与业务任务耦合在一起。
不方便将任务交给其他线程执行。
因此,这种方式不是特别推荐。
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 的优势:
任务与线程分离,降低耦合。
不占用 Java 类的继承关系。
一个 Runnable 任务可以交给不同线程执行。
可以方便地配合线程池使用。
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 的区别:
示例:
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();这只是普通的方法调用,不会创建新线程。
任务会在当前调用线程中执行。
因此,第一次输出:
main2. 调用 start()
thread.start();这才是真正启动一个新线程。
JVM 启动新的执行线程,由新线程执行 run() 方法。
第二次输出:
Thread-03. 两者区别
重点:
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 自带的,不需要引入第三方依赖。
六种状态:
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();
}
}执行过程:
t1 获取 LOCK 锁。
t1 调用 sleep(5000),进入 TIMED_WAITING。
但是 sleep() 不会释放 LOCK 锁。
t2 尝试获取 LOCK 锁。
因为 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注意:
这是一张简化的状态转换图。
线程可以在 RUNNABLE、BLOCKED、WAITING 和 TIMED_WAITING 之间多次切换。
被唤醒不代表立即获得 CPU 执行。
线程进入 TERMINATED 后不能重新启动。
五、容易混淆的知识点:sleep() 和 wait()
这两个方法都可以让线程暂停执行,但它们有本质区别。
示例:sleep() 不释放锁
synchronized (LOCK) {
Thread.sleep(3000);
}线程虽然休眠了,但仍然持有 LOCK。
其他线程无法获取这把锁。
示例:wait() 释放锁
synchronized (LOCK) {
LOCK.wait();
}线程调用 wait() 后,会释放 LOCK,让其他线程有机会获取锁。
注意:wait() 被唤醒后,需要重新竞争并获得 LOCK,才能继续执行。
六、重点总结
继承 Thread、实现 Runnable、Callable + FutureTask 都是常见的线程执行方式。
Runnable 用于定义任务,Thread 用于启动线程并执行任务。
Callable 可以返回执行结果,并且可以声明抛出受检异常。
FutureTask 可以包装 Callable,同时实现 Runnable 和 Future。
FutureTask.get() 会阻塞调用线程,直到任务完成。
生产环境推荐使用线程池管理线程。
start() 会启动新线程,run() 只是普通方法调用。
一个 Thread 对象只能成功调用一次 start()。
Java 线程有六种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。
RUNNABLE 包含操作系统层面的就绪和运行状态。
BLOCKED 特指等待获取 synchronized 监视器锁。
sleep() 不释放已经持有的锁,wait() 会释放调用对象的监视器锁。
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。
