Java多线程面试题(1)

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
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上执行(实际运行)
      • 就绪状态(等待操作系统调度)
  • 如何区分线程阻塞的原因?
    • 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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值