客运真实模拟
44.51M · 2026-02-16
总结:进程=资源隔离单位,线程=执行单位。多线程比多进程轻量,但并发需要注意竞态、可见性和原子性问题。
| 维度 | 进程 | 线程 |
|---|---|---|
| 资源分配 | 操作系统分配资源的单位 | 共享所属进程的资源 |
| 独立性 | 进程间完全独立 | 线程间共享内存 / 变量等 |
| 开销 | 创建 / 销毁 / 切换开销大 | 创建 / 销毁 / 切换开销小 |
| 通信 | 需借助 IPC(管道 / 套接字) | 直接共享变量(需同步) |
| 稳定性 | 一个进程崩溃不影响其他 | 一个线程崩溃可能导致进程崩溃 |
Java 中创建线程有 3 种标准方式,核心是最终都要关联可执行的任务逻辑。
// 步骤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 方法。// 步骤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();
}
}
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();
}
}
}
start():由 JVM 启动一个新的 操作系统/虚拟机线程,并在新线程中调用 run()。这是创建并运行新线程的正确方式。run():只是普通方法调用,在当前调用线程中执行,不会创建新线程。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() | 直接执行线程的任务逻辑 | 否(主线程执行) | 可重复调用 |
Java 线程的生命周期由Thread.State枚举定义,共 6 个状态,状态流转如下:
start()。synchronized 锁)。Object.wait() / Thread.join()(无超时)/ LockSupport.park())。Thread.sleep(ms) / Object.wait(timeout) / Thread.join(timeout) / LockSupport.parkNanos)。run() 方法结束或抛出未捕获异常,线程终止。NEW --(start)--> RUNNABLERUNNABLE --(获得锁/运行)--> RUNNABLE(执行中)RUNNABLE --(进入 synchronized 且锁被占用)--> BLOCKEDRUNNABLE --(调用 sleep/wait/park with timeout)--> TIMED_WAITINGRUNNABLE --(调用 wait/park/join 无超时)--> WAITINGRUNNABLE --(返回/抛异常)--> 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 等更可控、更高效。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 |
| 作用范围 | 作用于当前线程(静态方法,无法指定其他线程) | 作用于持有当前对象锁的线程 |
| 使用场景 | 单纯的 “延时执行”,不涉及线程间通信 | 线程间通信(等待 / 唤醒机制),如生产者 - 消费者模型 |