Java 并发八股文 + 场景题答案
1. 实现一个线程有哪几种方式?
继承 Thread 类:重写
run()方法,直接调用start()启动线程。实现 Runnable 接口:实现
run()方法,将实例作为参数传入Thread构造函数。实现 Callable 接口:实现
call()方法(有返回值,可抛异常),通过FutureTask包装后提交给线程池或Thread。使用线程池(如
ExecutorService):通过submit()或execute()提交任务。
2. Runnable 和 Callable 的区别及实现原理
区别:
Runnable的run()无返回值,不能抛受检异常;Callable的call()有返回值(通过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:基于ReentrantLock和Condition,线程调用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. 静态代码块、继承、构造方法的输出顺序
顺序原则:
父类静态代码块(类加载时执行一次);
子类静态代码块;
父类构造块(实例化时执行,在构造函数前);
父类构造函数;
子类构造块;
子类构造函数。
示例:
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();