线程基础
进程和线程的区别?
| 维度 | 进程 | 线程 |
|---|---|---|
| 资源隔离 | 独立内存空间,互不干扰 | 共享进程的堆和方法区 |
| 开销 | 创建/切换开销大 | 更轻量,上下文切换只需保存寄存器和栈指针 |
| 通信 | IPC(管道/Socket等) | 直接读写共享变量(需同步) |
| 崩溃影响 | 不影响其他进程 | 未捕获异常可能导致整个进程终止 |
进程是资源分配的基本单位,线程是 CPU 调度的基本单位。
创建线程有几种方式?
1. 继承 Thread 类
java
class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " running");
}
}
public class Main {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
}2. 实现 Runnable 接口(推荐)
java
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " running");
}
}
public class Main {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
// Lambda 写法
new Thread(() -> System.out.println(Thread.currentThread().getName() + " running")).start();
}
}3. 实现 Callable + FutureTask(有返回值)
java
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(1000);
return "result from " + Thread.currentThread().getName();
}
}
public class Main {
public static void main(String[] args) throws Exception {
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread t = new Thread(futureTask);
t.start();
// get() 会阻塞直到线程执行完毕
String result = futureTask.get();
System.out.println(result);
}
}4. 线程池 ExecutorService
java
public class Main {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);
// 提交 Runnable(无返回值)
pool.execute(() -> System.out.println("execute: " + Thread.currentThread().getName()));
// 提交 Callable(有返回值)
Future<String> future = pool.submit(() -> {
Thread.sleep(1000);
return "result from " + Thread.currentThread().getName();
});
try {
System.out.println(future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
pool.shutdown();
}
}实际开发优先使用线程池,避免频繁创建和销毁。Runnable 优于继承 Thread,因为 Java 单继承限制。
start() 和 run() 的区别?
| start() | run() | |
|---|---|---|
| 行为 | 让 JVM 创建新线程,新线程异步调用 run() | 普通方法调用,当前线程同步执行 |
| 是否新线程 | 是 | 否,不会开启新线程 |
| 重复调用 | 同一个 Thread 对象调用两次会抛出 IllegalThreadStateException | 可以多次调用,就是普通方法 |
java
class MyThread extends Thread {
@Override
public void run() {
System.out.println("执行线程: " + Thread.currentThread().getName());
}
}
public class Main {
public static void main(String[] args) {
MyThread t = new MyThread();
// 调用 start() —— 开启新线程
t.start();
// 输出: 执行线程: Thread-0
// 调用 run() —— 只是在 main 线程中同步执行,没有新线程
t.run();
// 输出: 执行线程: main
}
}java
// 同一个 Thread 对象调用两次 start() 会抛异常
MyThread t = new MyThread();
t.start();
t.start(); // 抛出 IllegalThreadStateException直接调用 run() 和多线程没有任何关系,想要真正开启线程必须调用 start()。
线程有哪些状态?
Java 线程状态定义在 Thread.State 枚举中,一共 6 种:

| 状态 | 触发方式 | 恢复方式 |
|---|---|---|
| NEW | new Thread() | 调用 start() |
| RUNNABLE | start() 后 | — |
| BLOCKED | 等待 synchronized 锁 | 获得锁 |
| WAITING | wait() / join() / LockSupport.park() | notify() / unpark() |
| TIMED_WAITING | sleep(ms) / wait(ms) | 超时自动唤醒 |
| TERMINATED | run() 执行完毕 | — |
sleep()、wait()、yield()、join() 的区别?
| 所属 | 是否释放锁 | 行为 | |
|---|---|---|---|
sleep(ms) | Thread | 不释放 | 让当前线程休眠指定毫秒,超时自动恢复 RUNNABLE |
wait() | Object | 释放锁 | 必须在 synchronized 块内调用,释放锁进入 WAITING,需 notify 唤醒 |
yield() | Thread | 不释放 | 提示调度器让出 CPU,但不保证生效,线程仍然 RUNNABLE |
join() | Thread | 释放锁 | 调用线程等待目标线程执行完毕,内部由 wait 实现,会释放持有的锁 |
java
// join 典型用法:main 线程等待子线程执行完毕
Thread t = new Thread(() -> {
// 计算任务
System.out.println("子线程计算完成");
});
t.start();
t.join(); // main 线程在此等待 t 结束后才继续
System.out.println("main 继续执行");sleep 不释放锁,wait 会释放锁。
notify() 和 notifyAll() 区别?
| notify() | notifyAll() | |
|---|---|---|
| 行为 | 随机唤醒等待队列中的一个线程 | 唤醒所有等待队列中的线程 |
| 风险 | 其余线程继续等待,若唤醒的线程无法继续执行可能造成死锁或饥饿 | 所有线程重新竞争锁,只有一个能拿到 |
| 实际开发 | — | 优先使用,更安全 |
java
// 错误:用 if 检查条件 —— 虚假唤醒或多个线程被唤醒时条件已不满足
synchronized (lock) {
if (count == 0) {
lock.wait(); // 被唤醒后不再检查条件,可能出错
}
count--;
}
// 正确:用 while 检查条件 —— 每次被唤醒都重新检查
synchronized (lock) {
while (count == 0) {
lock.wait(); // 被唤醒后重新检查 count,避免虚假唤醒
}
count--;
}wait 必须配合 while 循环而不是 if,因为 notifyAll 唤醒后需要重新检查条件,避免虚假唤醒。