java面试题
2022年1月1日大约 5 分钟
面试题
java面试题
CopyOnWriteArrayList介绍
在写操作(add
、remove
等)时,不直接对原数据进行修改,而是先将原数据复制一份,然后在新复制的数据上执行写操作,最后将原数据引用指向新数据。这样做的好处是: 读操作(get
、iterator
等)可以不加锁,因为读取的数据始终是不变的。
CopyOnWriteArrayList 优缺点
优点
- 线程安全
CopyOnWriteArrayList 是线程安全的,由于写操作对原数据进行复制,因此写操作不会影响读操作,读操作可以不加锁,降低了并发冲突的概率。 - 不会抛出
ConcurrentModificationException
异常
由于读操作遍历的是不变的数组副本,因此不会抛出ConcurrentModificationException
异常。
缺点
- 写操作性能较低
每一次写操作都需要将元素复制一份,因此写操作的性能较低。 - 内存占用增加
每次写操作都需要创建一个新的数组副本,因此内存占用会增加,特别是当集合中有大量数据时,内存占用较高。 - 数据一致性问题
由于读操作遍历的是不变的数组副本,因此在对数组执行写操作期间,读操作可能读取到旧的数组数据,这涉及到数据一致性问题。 CopyOnWriteArrayList 使用场景 - 读多写少
- 因为写操作会复制新集合,性能较低。
- 集合不大
- 因为写操作需要复制整个集合,内存占用会较高。
- 实时性要求不高
- 因为可能会读取到旧的集合数据。
CyclicBarrier的理解
一般这个会和countdownLatch比较
CyclicBarrier 的概念
CyclicBarrier 是 Java 中的一种多线程协作工具,允许多个线程在一个屏障点等待,直到所有线程都到达后一起继续执行。与 CountDownLatch 的主要区别在于:
可重复使用
CyclicBarrier 在所有线程到达屏障点后会自动重置,可以用于处理多次需要等待的任务。可执行额外动作
CyclicBarrier 支持在所有线程到达屏障点时,执行一个可选的动作(由Runnable
指定),这为复杂场景提供了灵活性。
CyclicBarrier 的主要特点
重复使用
- 每当所有线程到达屏障点后,屏障会自动重置,可以在循环任务或阶段性任务中多次使用。
线程协调
- 可以让多个线程在屏障点同步,协调它们同时开始执行。这在分阶段任务或并发游戏中非常有用。
额外逻辑
- CyclicBarrier 提供了一个可选的构造参数:
Runnable
,用于指定所有线程到达屏障点时执行的额外动作。
- CyclicBarrier 提供了一个可选的构造参数:
使用注意事项
指定线程数量
- 在创建 CyclicBarrier 时,必须指定参与线程的数量。
同步点的实现
- 所有线程到达屏障点后,屏障解除阻塞,所有线程继续执行后续操作。
异常处理
- 若某个线程在等待过程中被中断或超时,会导致其他线程抛出异常。
适用场景对比
特性 | CyclicBarrier | CountDownLatch |
---|---|---|
是否可重用 | 是 | 否 |
线程数量 | 必须在创建时指定,并固定 | 可由计数器的值动态控制 |
额外动作 | 支持,在屏障点可以执行指定的 Runnable | 不支持 |
应用场景 | 多阶段任务、线程协作同步 | 一次性同步,如等待某些初始化完成后执行 |
CountDownLatch的理解
CountDownLatch 是 Java 中用于多线程协作的辅助类,可以让一个或多个线程等待其他线程完成某个任务后再继续执行。
工作原理
CountDownLatch 通过一个计数器实现:
- 创建时设置计数器初始值,表示需要等待的线程数量或任务数量。
- 每个线程完成任务后调用
countDown()
方法,计数器减 1。 - 当计数器的值减至 0 时,所有在
await()
方法上等待的线程将被唤醒,继续执行后续操作。
CountDownLatch 的主要作用
主线程等待多个子线程完成任务
- 主线程调用
await()
方法等待,子线程完成任务后调用countDown()
,当所有子线程完成任务后,主线程被唤醒执行后续逻辑。
- 主线程调用
多个线程等待外部事件的发生
- 多个线程可以同时等待某个共同的事件,例如等待资源准备就绪或信号触发。
控制并发任务的同时开始
- 在某些并发场景中,需要等待所有线程都准备就绪后才能同时开始执行任务,CountDownLatch提供了一种便捷的方式来实现这一需求。
CountDownLatch 的特点
一次性使用
- CountDownLatch 的计数器无法重置。一旦计数器减至 0,就无法再次使用。如果需要重复使用计数器,应使用 CyclicBarrier。
线程安全
- CountDownLatch 内部通过 共享锁机制 保证线程安全。
实现灵活
- 可以协调单个线程与多个线程、多个线程之间的执行顺序。
CountDownLatch 与 CyclicBarrier 的对比
特性 | CountDownLatch | CyclicBarrier |
---|---|---|
是否可重用 | 否 | 是 |
计数器行为 | 单向递减,无法重置 | 固定线程数,到达后屏障重置 |
等待目标 | 线程等待其他线程完成 | 线程相互等待,直到所有线程到达屏障点 |
额外动作 | 不支持 | 支持,屏障点可执行指定的 Runnable |
应用场景 | 等待任务完成、控制执行顺序 | 多线程同步起点,分阶段任务 |
实现机制 | 借助共享锁,计数器递减至 0 时唤醒线程 | 借助条件变量,所有线程到达屏障点后同时执行 |
使用场景示例
- 主线程等待多个子线程完成任务
CountDownLatch latch = new CountDownLatch(3); for (int i = 0; i < 3; i++) { new Thread(() -> { try { // 模拟子线程任务 System.out.println(Thread.currentThread().getName() + " 正在执行任务"); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + " 任务完成"); } catch (InterruptedException e) { e.printStackTrace(); } finally { latch.countDown(); // 任务完成,计数器减 1 } }).start(); } System.out.println("主线程等待子线程完成..."); latch.await(); // 等待计数器归 0 System.out.println("所有子线程完成任务,主线程继续执行");