1.并行和并发有什么区别?
(1)核心区别
- 并发
- 定义:多个任务在重叠的时间段内执行
- 必要条件:需要任务调度
- 目标:提高系统资源利用率
- 实现层面:主要是软件设计概念
- 典型场景:I/O密集型任务
- 关系:并发包含并行(并行是并发的子集)
- 并行
- 定义:多个任务真正同时执行
- 必要条件:需要多核/多处理器硬件支持
- 目标:提高计算吞吐量
- 实现层面:主要是硬件执行特征
- 典型场景:CPU密集型任务
- 关系:并行是实现并发的一种方式
(2)常见误区
- 多线程就是并行?
- 单核CPU上的多线程是并发而非并行
- 并发总是比串行快?
- 并发可能因上下文切换反而更慢
- 并行不需要考虑竞态条件?
- 并行必须处理同步问题
2.线程和进程的区别?
(1)核心区别
- 进程
- 定义:程序执行的实例,拥有独立内存空间
- 资源分配:系统独立分配内存和资源
- 创建开销:大(需要分配独立资源)
- 切换成本:高(需要切换内存空间)
- 通信机制:管道、消息队列、共享内存等
- 独立性:一个进程崩溃不影响其他进程
- 并发性:进程间并发
- 安全性:更安全(内存隔离)
- 线程
- 定义:进程内的执行单元,共享进程资源
- 资源分配:共享所属进程的内存和资源
- 创建开销:小(只需分配栈和寄存器)
- 切换成本:低(共享相同地址空间)
- 通信机制:直接读写进程内存(需同步)
- 独立性:一个线程崩溃可能导致整个进程终止
- 并发性:线程间并发(更轻量)
- 安全性:需要同步机制保证安全
(2)创建
- 进程创建
// 创建子进程
ProcessBuilder pb = new ProcessBuilder("java", "-version");
Process process = pb.start();
process.waitFor(); // 等待子进程结束
- 线程创建
// 方式1:继承Thread类
class MyThread extends Thread {
public void run() {
System.out.println("线程运行中");
}
}
new MyThread().start();
// 方式2:实现Runnable接口
new Thread(() -> System.out.println("Lambda线程")).start();
(3)通信方式
- 进程间通信(IPC)
- 管道(Pipe)
PipedOutputStream pos = new PipedOutputStream();
PipedInputStream pis = new PipedInputStream(pos);
// 一个进程写pos,另一个读pis
- Socket通信
// 服务端
ServerSocket server = new ServerSocket(8080);
Socket client = server.accept();
// 客户端
Socket socket = new Socket("localhost", 8080);
- 线程间通信
- 共享变量
class Shared {
public static volatile int value;
}
// 线程A
Shared.value = 1;
// 线程B
int readValue = Shared.value;
- 等待/通知机制
synchronized(lock) {
lock.wait(); // 线程A等待
lock.notify(); // 线程B唤醒
}
(4)适用场景
- 进程
- 需要高安全隔离性(如浏览器多标签页)
- 需要利用多核并行计算(如科学计算)
- 长时间运行的独立服务(如数据库服务)
- 需要利用操作系统进程管理功能(如守护进程)
- 线程
- 高并发I/O操作(如Web服务器)
- 需要频繁创建销毁执行单元
- 任务间需要高效数据共享
- 图形界面程序(保持UI响应)
3.守护线程是什么?
(1)核心特性
- 守护线程
- 生命周期:随JVM退出而终止(不阻止JVM退出)
- 用途:后台支持任务(GC、监控等)
- 优先级:通常较低
- 创建方式:setDaemon(true)在启动前调用
- 异常处理:未捕获异常不会导致JVM退出
- 用户线程
- 生命周期:JVM需等待所有用户线程结束才会退出
- 用途:主程序业务逻辑
- 优先级:根据业务需求设置
- 创建方式:默认类型(或显式setDaemon(false))
- 异常处理:未捕获异常可能导致线程终止
(2)创建与使用
- 基础创建
Thread daemonThread = new Thread(() -> {
while (true) {
System.out.println("守护线程运行中...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("守护线程被中断");
}
}
});
// 必须在线程启动前设置为守护线程
daemonThread.setDaemon(true);
daemonThread.start();
// 主线程(用户线程)结束即退出JVM
Thread.sleep(3000);
System.out.println("主线程结束,JVM将退出");
- 应用场景
- 垃圾回收:JVM的GC线程
- 内存管理:缓存过期清理
- 监控统计:定期收集性能指标
- 心跳检测:维持长连接活性
(3)注意事项
- 设置时机
Thread t = new Thread();
t.start();
t.setDaemon(true); // 错误!抛出IllegalThreadStateException
- finally块执行
Thread daemon = new Thread(() -> {
try {
// 业务逻辑
} finally {
System.out.println("不保证执行"); // JVM退出时可能跳过
}
});
- 线程池中的守护线程
ExecutorService daemonExecutor = Executors.newFixedThreadPool(4, r -> {
Thread t = new Thread(r);
t.setDaemon(true); // 正确设置方式
return t;
});
(4)扩展问题
- 为什么我的守护线程突然终止了?
- 当所有用户线程执行完毕时,JVM会立即退出,导致守护线程被强制终止,无论其是否执行完成
- 守护线程创建的线程是什么类型?
- 新建线程继承父线程的守护状态,但可通过setDaemon()修改
Thread parent = new Thread(() -> {
Thread child = new Thread();
System.out.println(child.isDaemon()); // 输出true
});
parent.setDaemon(true);
parent.start();
4.创建线程有哪几种方式?
(1)基础创建方式
- 继承Thread类(最基础)
- 特点
- 简单直接
- 单继承限制(Java不允许多继承)
- 不符合"组合优于继承"原则
- 返回值:无
- 异常处理:自行处理
- 资源消耗:高
- 特点
class MyThread extends Thread {
@Override
public void run() {
System.out.println("继承Thread类的线程执行");
}
}
// 使用方式
MyThread thread = new MyThread();
thread.start(); // 启动线程
- 实现Runnable接口(推荐)
- 优势
- 避免继承限制
- 更适合资源共享
- Java 8+可使用Lambda简化
- 返回值:无
- 异常处理:自行处理
- 资源消耗:中
- 优势
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("实现Runnable接口的线程执行");
}
}
// 使用方式
Thread thread = new Thread(new MyRunnable());
thread.start();
(2)高级创建方式
- 实现Callable接口(带返回值)
- 特点
- 可返回结果
- 可抛出异常
- 通常配合线程池使用
- 返回值:有
- 异常处理:Future获取
- 资源消耗:中
- 特点
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(1000);
return "Callable线程执行完成";
}
}
// 使用方式
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new MyCallable());
System.out.println(future.get()); // 阻塞获取返回值
executor.shutdown();
- 使用线程池(生产环境推荐)
- 优势
- 降低资源消耗(线程复用)
- 提高响应速度(无需新建线程)
- 提供任务队列和拒绝策略
- 返回值:可选
- 异常处理:灵活处理
- 资源消耗:低
- 优势
ExecutorService threadPool = Executors.newFixedThreadPool(5);
// 提交Runnable任务
threadPool.execute(() -> {
System.out.println("线程池执行Runnable任务");
});
// 提交Callable任务
Future<Integer> future = threadPool.submit(() -> {
return 1 + 1;
});
System.out.println("计算结果: " + future.get());
threadPool.shutdown(); // 优雅关闭
(3)现代创建方式(Java 21+)
- 虚拟线程
- 革命性特点
- 轻量级(开销极小,可创建数百万个)
- 由JVM调度(非操作系统线程)
- 简化高并发编程
- 返回值:可选
- 异常处理:灵活处理
- 资源消耗:极低
- 革命性特点
// 方式1:直接启动
Thread.startVirtualThread(() -> {
System.out.println("轻量级虚拟线程运行");
});
// 方式2:使用Builder
Thread.ofVirtual()
.name("my-virtual-thread")
.start(() -> System.out.println("命名的虚拟线程"));
5.说一下 runnable 和 callable 有什么区别?
(1)使用方式
- Runnable
// 实现类方式
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable执行");
}
}
new Thread(new MyRunnable()).start();
// Lambda简化方式
new Thread(() -> System.out.println("Lambda Runnable")).start();
- Callable
// 实现类方式
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable结果";
}
}
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new MyCallable());
System.out.println(future.get()); // 获取返回值
executor.shutdown();
(2)功能差异
- 返回值处理
- Runnable
Runnable task = () -> {
int result = 1 + 1;
// 无法直接返回结果,需要额外处理
};
new Thread(task).start();
- Callable
Callable<Integer> task = () -> {
return 1 + 1; // 直接返回计算结果
};
Future<Integer> future = executor.submit(task);
int sum = future.get(); // 获取返回值
- 异常处理
- Runnable
Runnable task = () -> {
try {
Files.readAllLines(Paths.get("nonexistent.txt"));
} catch (IOException e) {
// 必须在此处理,无法向上抛出
System.err.println("文件读取失败");
}
};
- Callable
Callable<List<String>> task = () -> {
// 可以直接抛出异常
return Files.readAllLines(Paths.get("nonexistent.txt"));
};
try {
future.get();
} catch (ExecutionException e) {
System.err.println("捕获到调用异常: " + e.getCause());
}
(3)执行机制
- 线程池提交方式
- Executor.execute()
- Runnable:支持
- Callable:不支持
- ExecutorService.submit()
- Runnable:返回Future
- Callable:返回Future
- Executor.execute()
ExecutorService executor = Executors.newCachedThreadPool();
// 提交Runnable
Future<?> runnableFuture = executor.submit(() -> System.out.println("Runnable"));
// 提交Callable
Future<String> callableFuture = executor.submit(() -> "Callable Result");
(4)应用场景
- Runnable
- 简单的日志记录
- 异步事件通知
- 不需要返回值的后台任务
- 兼容旧版Java代码(<1.5)
// 定时任务
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(
() -> System.out.println("定时日志"),
0, 1, TimeUnit.SECONDS
);
- Callable
- 需要获取计算结果的任务
- 可能抛出异常的需要异常传播的任务
- 需要取消执行或超时控制的任务
- 并行计算场景
// 并行计算多个API调用
List<Callable<String>> tasks = Arrays.asList(
() -> apiClient.fetchUserInfo(),
() -> apiClient.fetchOrderHistory()
);
List<Future<String>> futures = executor.invokeAll(tasks);
for (Future<String> future : futures) {
System.out.println(future.get());
}
6.线程有哪些状态?
(1)各状态详解
- NEW(新建状态)
Thread thread = new Thread(() -> {
System.out.println("线程执行");
});
System.out.println(thread.getState()); // 输出 NEW
- RUNNABLE(可运行状态)
thread.start();
System.out.println(thread.getState()); // 可能输出 RUNNABLE
注:可运行状态包括正在运行和就绪两种子状态
- BLOCKED(阻塞状态)
Object lock = new Object();
Thread t1 = new Thread(() -> {
synchronized (lock) {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) { // t2将在此处阻塞
System.out.println("获取到锁");
}
});
t1.start();
t2.start();
Thread.sleep(100); // 确保t1先获取锁
System.out.println(t2.getState()); // 输出 BLOCKED
- WAITING(无限等待)
Thread t = new Thread(() -> {
synchronized (lock) {
try {
lock.wait(); // 进入WAITING状态
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
t.start();
Thread.sleep(100);
System.out.println(t.getState()); // 输出 WAITING
- TIMED_WAITING(限期等待)
Thread t = new Thread(() -> {
try {
Thread.sleep(1000); // 带超时的等待
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
t.start();
Thread.sleep(100);
System.out.println(t.getState()); // 输出 TIMED_WAITING
- TERMINATED(终止状态)
Thread t = new Thread(() -> {});
t.start();
t.join(); // 等待线程结束
System.out.println(t.getState()); // 输出 TERMINATED
(2)状态转换
- NEW -> RUNNABLE
- 调用start()方法
- RUNNABLE -> BLOCKED
- 尝试进入synchronized同步块(锁被其他线程持有)
- BLOCKED -> RUNNABLE
- 获取到监视器锁
- RUNNABLE -> WAITING
- 调用Object.wait()、Thread.join()或LockSupport.park()
- WAITING -> RUNNABLE
- 被notify()/notifyAll()唤醒或目标线程终止(join()情况)
- RUNNABLE -> TIMED_WAITING
- 调用Thread.sleep()、Object.wait(timeout)、Thread.join(timeout)等带超时方法
- TIMED_WAITING -> RUNNABLE
- 超时时间到或被唤醒
- -> TERMINATED
- run()方法执行完成或抛出未捕获异常
(3)扩展问题
- RUNNABLE状态是否意味着线程正在运行?
- 不一定。RUNNABLE状态包括:
- 正在CPU上执行(实际运行)
- 就绪状态(等待操作系统调度)
- 不一定。RUNNABLE状态包括:
- 如何区分线程阻塞的原因?
- BLOCKED:等待获取synchronized锁
- WAITING/TIMED_WAITING:等待其他条件(通知、超时等)
7.sleep()和wait()的状态区别
(1)sleep()(Thread类的方法)
- 是Thread类的静态方法
- 让当前线程暂停执行指定的时间,但不释放锁
- 主要用于时间控制
- 必须指定睡眠时间
- 睡眠结束后线程进入就绪状态
Thread.sleep(1000); // 暂停1秒
(2)wait()(Object类的方法)
- 是Object类的方法
- 使当前线程等待,直到其他线程调用notify()或notifyAll()
- 会释放对象锁
- 必须在同步块(synchronized)中使用
- 可以指定超时时间或不指定
synchronized(obj) {
obj.wait(); // 释放obj的锁并等待
}
8.notify()和 notifyAll()有什么区别?
(1)notify()
- 随机选择一个在该对象上等待的线程进行唤醒
- 被唤醒的线程会尝试重新获取对象锁
- 如果多个线程在等待,无法控制具体唤醒哪一个
- 更高效,因为只唤醒一个线程
(2)notifyAll()
- 唤醒所有在该对象上等待的线程
- 所有被唤醒的线程会竞争对象锁
- 最终只有一个线程能获取锁,其他线程会继续等待
- 更安全但效率较低,因为会唤醒所有线程
(3)适用场景
- notify()
- 生产者-消费者模型中,生产者生产了一个产品,只需要唤醒一个消费者
- 线程池中,有新任务到来时只需唤醒应该工作线程
- notifyAll()
- 资源状态改变,所有等待线程都需要重新检查条件
- 多个线程等待不同条件,不确定该唤醒哪一个
- 避免某些线程被“饿死”的情况
注:
(1)在调用这些方法前,线程必须持有对象锁(即在同步块中)
(2)被唤醒的线程不会立即执行,必须等待当前线程退出同步块
(3)通常应与 while 循环配合使用,防止"虚假唤醒"
9.线程的 run()和 start()有什么区别?
(1)run() 方法
- 只是Thread类的一个普通方法
- 直接调用时会在当前线程中执行
- 不会启动新线程
Thread t = new Thread(() -> {
System.out.println("Running in: " + Thread.currentThread().getName());
});
t.run(); // 输出:Running in: main (在主线程执行)
(2)start() 方法
- 是启动新线程的唯一正确方式
- 会创建新线程并在新线程中调用 run()
- 涉及线程调度,执行顺序不确定
Thread t = new Thread(() -> {
System.out.println("Running in: " + Thread.currentThread().getName());
});
t.start(); // 输出:Running in: Thread-0 (在新线程执行)
(3)常见误区
- 调用 start() 会直接执行 run() 方法?
- start() 会向JVM注册新线程
- JVM在适当时候调用 run()
- start() 方法本身会立即返回(不会阻塞调用它的线程)
- 为什么不能直接调用run()?
- 失去了多线程的意义
- 仍然是单线程顺序执行
- 无法利用多核CPU优势
10.创建线程池有哪几种方式?
(1)通过 Executors 工厂类创建(推荐)
- 固定大小线程池(FixedThreadPool)
- 特点
- 线程数量固定
- 无界队列(LinkedBlockingQueue)
- 适用于负载较重的服务器
- 特点
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5); // 固定5个线程
- 单线程线程池(SingleThreadExecutor)
- 特点
- 只有一个工作线程
- 保证任务顺序执行
- 无界队列
- 特点
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
- 可缓存线程池(CachedThreadPool)
- 特点
- 线程数量可动态调整(0到Integer.MAX_VALUE)
- 适合执行大量短期异步任务
- 使用SynchronousQueue
- 特点
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
- 定时任务线程池(ScheduledThreadPool)
- 特点
- 可定时或周期性执行任务
- 适用于定时任务和延迟任务
- 特点
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
(2)直接通过 ThreadPoolExecutor创建(更灵活)
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
60L, // 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(100), // 任务队列
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
- 参数说明:
- corePoolSize: 核心线程数
- maximumPoolSize: 最大线程数
- keepAliveTime: 空闲线程存活时间
- workQueue: 任务队列
- threadFactory: 线程工厂
- handler: 拒绝策略
(3)ForkJoinPool (Java 7+)
- 适用于分治算法的线程池
ForkJoinPool forkJoinPool = new ForkJoinPool();
(4)选择建议
- Executors 工厂方法:适合简单场景,使用方便
- ThreadPoolExecutor:需要更精细控制时使用
- ForkJoinPool:适合计算密集型任务和分治算法
注:
(1)避免使用无界队列,可能导致OOM
(2)根据任务类型选择合适的线程池
(3)合理设置线程池大小(CPU密集型 vs IO密集型)
(4)注意关闭线程池(shutdown/shutdownNow)