一、进程 vs 线程
进程
-
操作系统分配资源(内存、文件句柄、堆栈、地址空间等)的基本单位,可以理解为 "一个正在运行的程序"。一个进程至少包含一个线程(主线程)。进程间相互隔离,默认不能直接访问彼此内存(需要IPC:管道、Socket、共享内存等)。
线程
-
CPU 调度的基本单位,是进程内的执行流,是进程的 "最小执行单元"。线程共享进程的地址空间(堆、字符串常量池),但有自己的栈(本地方法栈和虚拟机栈)和程序计数器。线程创建开销比进程小,切换成本也低,但需要处理共享数据的一致性(同步问题)。
-
总结:进程=资源隔离单位,线程=执行单位。多线程比多进程轻量,但并发需要注意竞态、可见性和原子性问题。
关键区别(表格对比)
| 维度 | 进程 | 线程 |
|---|---|---|
| 资源分配 | 操作系统分配资源的单位 | 共享所属进程的资源 |
| 独立性 | 进程间完全独立 | 线程间共享内存 / 变量等 |
| 开销 | 创建 / 销毁 / 切换开销大 | 创建 / 销毁 / 切换开销小 |
| 通信 | 需借助 IPC(管道 / 套接字) | 直接共享变量(需同步) |
| 稳定性 | 一个进程崩溃不影响其他 | 一个线程崩溃可能导致进程崩溃 |
举例
- 进程:一家餐厅(独立的资源,如场地、厨具、食材)。
- 线程:餐厅里的厨师、服务员、收银员(共享餐厅资源,各自执行不同任务)。
二、创建线程的 3 种方式(完整代码示例)
Java 中创建线程有 3 种标准方式,核心是最终都要关联可执行的任务逻辑。
方式 1:继承 Thread 类(重写 run () 方法)
java
// 步骤1:继承Thread类
class MyThread extends Thread {
// 步骤2:重写run()方法,定义线程执行逻辑
@Override
public void run() {
for (int i = 0; i < 5; i++) {
// Thread.currentThread().getName() 获取当前线程名称
System.out.println(Thread.currentThread().getName() + " 执行:" + i);
try {
// 模拟耗时操作
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 测试类
public class ThreadCreateDemo1 {
public static void main(String[] args) {
// 步骤3:创建线程对象
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
// 步骤4:启动线程(必须调用start(),而非直接run())
thread1.setName("线程1");
thread2.setName("线程2");
thread1.start();
thread2.start();
}
}
-
优点:直观,能直接调用
this.getName()等Thread方法。 -
缺点:不能再继承其他类(Java 单继承),职责耦合较高,不够灵活。
方式 2:实现 Runnable 接口(推荐,解耦任务与线程)
java
// 步骤1:实现Runnable接口
class MyRunnable implements Runnable {
// 步骤2:实现run()方法,定义任务逻辑
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " 执行:" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 测试类
public class ThreadCreateDemo2 {
public static void main(String[] args) {
// 步骤3:创建任务对象
MyRunnable task = new MyRunnable();
// 步骤4:将任务传入Thread对象
Thread thread1 = new Thread(task, "线程A");
Thread thread2 = new Thread(task, "线程B");
// 步骤5:启动线程
thread1.start();
thread2.start();
}
}
-
优点:和业务逻辑解耦,可复用对象(实现单一职责),可以传递给线程池等。
-
缺点:不能直接返回结果(但可通过共享变量 / Future 等方式)。
方式 3:实现 Callable 接口(带返回值 + 可抛异常)
java
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
// 步骤1:实现Callable接口(指定返回值类型)
class MyCallable implements Callable<Integer> {
// 步骤2:实现call()方法(带返回值、可抛异常)
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
Thread.sleep(10);
}
return sum; // 返回计算结果
}
}
// 测试类
public class ThreadCreateDemo3 {
public static void main(String[] args) {
// 步骤3:创建Callable任务对象
MyCallable callable = new MyCallable();
// 步骤4:通过FutureTask包装Callable(用于接收返回值)
FutureTask<Integer> futureTask = new FutureTask<>(callable);
// 步骤5:将FutureTask传入Thread
Thread thread = new Thread(futureTask, "计算线程");
thread.start();
// 步骤6:获取返回值(get()会阻塞,直到线程执行完成)
try {
Integer result = futureTask.get();
System.out.println("线程执行结果:1-100求和 = " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
-
优点:可以返回结果、抛出异常;与线程池配合能高效管理线程资源(推荐用于生产代码)。
-
缺点:需要管理线程池生命周期(shutdown 等)。
三、start () vs run () 核心区别
-
start():由 JVM 启动一个新的 操作系统/虚拟机线程 ,并在新线程中调用run()。这是创建并运行新线程的正确方式。 -
run():只是普通方法调用,在当前调用线程中执行,不会创建新线程。
java
public class StartVsRun {
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("Inside run(): " + Thread.currentThread().getName());
}, "worker");
t.start(); // 输出:Inside run(): worker (在新线程)
// vs
t.run(); // 输出:Inside run(): main (在当前线程,未创建新线程)
}
}
| 方法 | 核心行为 | 是否创建新线程 | 调用方式 |
|---|---|---|---|
| start() | 告诉 JVM 启动线程,由 JVM 调用 run () | 是(真正并发) | 只能调用 1 次(多次抛异常) |
| run() | 直接执行线程的任务逻辑 | 否(主线程执行) | 可重复调用 |
四、线程生命周期(6 个状态)
Java 线程的生命周期由Thread.State枚举定义,共 6 个状态,状态流转如下:
-
NEW --- 线程已创建,但尚未调用
start()。 -
RUNNABLE --- 线程可运行(在 Java 层是可运行状态),可能正在运行或在就绪队列等待 CPU。
-
BLOCKED --- 线程被阻塞,等待进入某个监视器(即等待
synchronized锁)。 -
WAITING --- 无期限等待(例如
Object.wait()/Thread.join()(无超时)/LockSupport.park())。 -
TIMED_WAITING --- 有期限等待(例如
Thread.sleep(ms)/Object.wait(timeout)/Thread.join(timeout)/LockSupport.parkNanos)。 -
TERMINATED ---
run()方法结束或抛出未捕获异常,线程终止。

常见状态转换(简化)
-
NEW--(start)-->RUNNABLE -
RUNNABLE--(获得锁/运行)-->RUNNABLE(执行中) -
RUNNABLE--(进入 synchronized 且锁被占用)-->BLOCKED -
RUNNABLE--(调用 sleep/wait/park with timeout)-->TIMED_WAITING -
RUNNABLE--(调用 wait/park/join 无超时)-->WAITING -
RUNNABLE--(返回/抛异常)-->TERMINATED
示例:synchronized 导致 BLOCKED;sleep 导致 TIMED_WAITING;另一个线程 interrupt() 在 sleep/wait 会抛 InterruptedException。
五、线程常用方法都有哪些?
-
创建/控制
-
start():启动新线程。 -
run():线程执行体;直接调用不会产生新线程。 -
isAlive():线程是否仍存活(未进入TERMINATED)。 -
getName()/setName(String):线程名。 -
getId():线程 id(JVM 分配)。 -
setDaemon(boolean)/isDaemon():设置守护线程(JVM 在只剩守护线程时会退出)。 -
getPriority()/setPriority(int):线程优先级(平台相关,通常不推荐依赖)。
-
-
等待 / 睡眠 / 同步相关
-
sleep(long millis):静态方法,使当前线程休眠(抛InterruptedException)。Thread.sleep(1000); -
yield():提示线程调度器当前线程愿意放弃 CPU,但不保证立即发生。 -
join()/join(long):等待目标线程终止(阻塞当前线程),常用于线程间等待结果。 -
wait()/wait(long)/notify()/notifyAll():Object的方法,用于线程间协作(必须在同步块内调用)。
-
-
中断
-
interrupt():向目标线程发送中断标志(不会强制停止线程),用于协作式取消。 -
isInterrupted():查询线程的中断状态(不清除)。 -
Thread.interrupted():静态方法,检查并清除当前线程的中断状态。 -
在阻塞方法(
sleep/wait/join)中,若线程被中断,会抛InterruptedException,通常需要处理并决定是否传播中断(最好:catch 后重新设置中断Thread.currentThread().interrupt();)。
-
-
状态与异常
-
getState():返回Thread.State。 -
setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler):处理线程未捕获异常。
-
-
已弃用 / 不安全的方法
suspend()/resume()/stop():已弃用,不要使用,会导致死锁或不一致状态。
-
并发工具(不是 Thread 的方法,但常用)
ExecutorService、Callable、Future、CompletableFuture、Lock(ReentrantLock)、AtomicInteger等更可控、更高效。
六、sleep方法与wait方法有什么区别?
Thread.sleep(long)是线程类的静态方法 ,作用于当前执行的线程;Object.wait()是所有对象的成员方法 ,必须在同步代码块 / 同步方法 中调用(持有对象锁),作用于持有该对象锁的线程。
常见误区纠正
-
误区 1 :"wait () 后线程立即执行" 错误:wait () 被唤醒后,线程会进入BLOCKED状态(等待重新获取锁),只有获取到锁后,才会回到 RUNNABLE 状态执行; 正确:notify () 只是 "唤醒通知",不是 "直接执行"。
-
误区 2 :"sleep (0) 没用" 错误:
Thread.sleep(0)会触发 CPU 重新调度,让当前线程让出 CPU 执行权,给其他同优先级线程执行机会; 场景:用于多线程公平调度(如轮询任务)。 -
误区 3 :"wait () 可以不在 synchronized 中调用" 错误:JVM 强制要求 wait ()/notify ()/notifyAll () 必须在持有对象锁时调用,否则直接抛
IllegalMonitorStateException; 原理:防止 "虚假唤醒",保证等待 / 唤醒的原子性。
| 对比维度 | Thread.sleep(long millis) | Object.wait() / Object.wait(long timeout) |
|---|---|---|
| 所属类 | Thread(静态方法) | Object(成员方法,所有对象都有) |
| 锁的处理 | 不释放持有的锁(如果当前线程持有锁,休眠时锁仍保留) | 必须释放持有的对象锁(等待期间锁归还给其他线程) |
| 调用前提 | 无强制要求,任何地方都可调用 | 必须在 synchronized 代码块 / 方法中调用(否则抛 IllegalMonitorStateException) |
| 唤醒方式 | 1. 超时自动唤醒;2. 被中断(interrupt())唤醒 |
1. 其他线程调用 object.notify()/notifyAll();2. 超时自动唤醒(带参版本);3. 被中断唤醒 |
| 线程状态 | 进入 TIMED_WAITING(超时等待)状态 |
无参版:WAITING(等待);带参版:TIMED_WAITING |
| 作用范围 | 作用于当前线程(静态方法,无法指定其他线程) | 作用于持有当前对象锁的线程 |
| 使用场景 | 单纯的 "延时执行",不涉及线程间通信 | 线程间通信(等待 / 唤醒机制),如生产者 - 消费者模型 |
