Administrator
发布于 2025-09-29 / 2 阅读
0
0

Java面试题

Java 并发八股文 + 场景题答案


1. 实现一个线程有哪几种方式?

  • 继承 Thread 类:重写 run() 方法,直接调用 start() 启动线程。

  • 实现 Runnable 接口:实现 run() 方法,将实例作为参数传入 Thread 构造函数。

  • 实现 Callable 接口:实现 call() 方法(有返回值,可抛异常),通过 FutureTask 包装后提交给线程池或 Thread

  • 使用线程池(如 ExecutorService):通过 submit()execute() 提交任务。


2. Runnable 和 Callable 的区别及实现原理

  • 区别

    • Runnablerun() 无返回值,不能抛受检异常;

    • Callablecall() 有返回值(通过 Future 获取),可抛异常。

  • 实现原理

    • Runnable 直接由 Thread 执行,任务结果无法直接获取;

    • Callable 通常被包装成 FutureTask(实现了 RunnableFuture,即 Runnable + Future),内部通过 state 状态机管理任务生命周期(未启动、完成、异常等),返回值通过 get() 阻塞获取(底层是 LockSupport.park() 等待唤醒机制)。


3. 控制三个线程顺序执行(A→B→C)

  • 方法1:使用 join() 在 B 线程中调用 A.join(),在 C 线程中调用 B.join(),确保等待前一个线程结束。

  • 方法2:使用单线程池(Executors.newSingleThreadExecutor() 按顺序提交任务,池中线程按提交顺序执行。

  • 方法3:使用 CountDownLatch 定义两个 latch:latch1(初始1)和 latch2(初始1)。

    • A 执行后 latch1.countDown()

    • B 等待 latch1.await(),执行后 latch2.countDown()

    • C 等待 latch2.await() 后执行。


4. CountDownLatch 和 CyclicBarrier 的区别及内部实现

  • 区别

    • CountDownLatch:一次性的,等待指定数量的线程完成(减到0后唤醒等待线程);

    • CyclicBarrier:可重复使用,等待指定数量的线程到达屏障点,然后同时执行后续任务(可指定回调)。

  • 内部实现

    • CountDownLatch:基于 AQS(AbstractQueuedSynchronizer),state 表示剩余计数,await() 阻塞直到 state=0。

    • CyclicBarrier:基于 ReentrantLockCondition,线程调用 await() 时计数减1,未到0时在 Condition 上等待;到0后唤醒所有线程并重置计数(可复用)。


5. Redis 找出固定前缀的 10 万个 key

  • 使用 SCAN 命令(非阻塞,避免 KEYS 阻塞服务):

    SCAN 0 MATCH "prefix:*" COUNT 1000

    迭代遍历(游标0开始,下次用返回的新游标),每次返回部分 key,客户端累计筛选。

  • 注意:可能重复,需客户端去重;COUNT 值可调整(网络和性能权衡)。


6. Redis 仅存 20 万热点数据(数据库有 2000 万)

  • 策略

    • 设置最大内存(maxmemory)并选择淘汰策略(如 allkeys-lru):优先淘汰最近未使用的 key。

    • 对数据访问加缓存:先查缓存,不存在则从数据库加载并写入 Redis(同时设置过期时间)。

    • 对热点数据手动管理(如定时刷新、延长过期时间)。

  • 保证热点:监控缓存命中率,调整淘汰策略或扩容。


7. String s = new String("abc") 创建了几个对象?

  • 情况1:如果常量池已存在 "abc",则只创建 1 个对象(堆中的 String 实例);

  • 情况2:如果常量池不存在 "abc",则创建 2 个对象(常量池中的 "abc" 和堆中的 String 实例)。 (注意:JDK7+ 常量池在堆中,但逻辑上仍区分)


8. 静态代码块、继承、构造方法的输出顺序

  • 顺序原则

    1. 父类静态代码块(类加载时执行一次);

    2. 子类静态代码块;

    3. 父类构造块(实例化时执行,在构造函数前);

    4. 父类构造函数;

    5. 子类构造块;

    6. 子类构造函数。

  • 示例

    
    class Parent {
        static { System.out.println("Parent static block"); }
        { System.out.println("Parent block"); }
        Parent() { System.out.println("Parent constructor"); }
    }
    class Child extends Parent {
        static { System.out.println("Child static block"); }
        { System.out.println("Child block"); }
        Child() { System.out.println("Child constructor"); }
    }
    // 输出顺序:
    // Parent static block → Child static block → 
    // Parent block → Parent constructor → 
    // Child block → Child constructor

手撕代码部分

1. SQL:查询前一个月下单量最多的三天


SELECT DATE(order_time) AS order_date, COUNT(*) AS order_count
FROM orders
WHERE order_time >= DATE_SUB(CURDATE(), INTERVAL 1 MONTH)
GROUP BY order_date
ORDER BY order_count DESC
LIMIT 3;
  • 假设表名为 orders,时间字段为 order_time


2. 保证线程输出顺序(A→B→C)

// 方法1:使用 join()
Thread a = new Thread(() -> System.out.println("A"));
Thread b = new Thread(() -> {
    try { a.join(); } catch (InterruptedException e) {}
    System.out.println("B");
});
Thread c = new Thread(() -> {
    try { b.join(); } catch (InterruptedException e) {}
    System.out.println("C");
});
a.start(); b.start(); c.start();
​
// 方法2:使用单线程池
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> System.out.println("A"));
executor.submit(() -> System.out.println("B"));
executor.submit(() -> System.out.println("C"));
executor.shutdown();


评论