Skip to content

并发集合面试题

1. 并发集合的基本概念

问题:什么是并发集合?为什么要使用并发集合?

答案

  • 并发集合:支持多线程并发访问的集合。
  • 作用
    • 保证线程安全。
    • 提高并发性能。
    • 避免手动同步。

2. ConcurrentHashMap

问题:ConcurrentHashMap的实现原理是什么?

答案

  • Java 7
    • 使用分段锁(Segment)。
    • 每个Segment维护一个HashEntry数组。
    • 并发度等于Segment的数量。
  • Java 8
    • 使用CAS + synchronized。
    • 每个桶使用Node数组。
    • 链表长度超过8时转换为红黑树。

3. ConcurrentHashMap和HashMap的区别

问题:ConcurrentHashMap和HashMap有什么区别?

答案

  • 线程安全:ConcurrentHashMap是线程安全的,HashMap不是。
  • null值:ConcurrentHashMap不允许null键和null值,HashMap允许。
  • 迭代器:ConcurrentHashMap的迭代器是弱一致的,HashMap的迭代器是快速失败的。
  • 性能:ConcurrentHashMap在并发场景下性能更好。

4. CopyOnWriteArrayList

问题:CopyOnWriteArrayList的实现原理是什么?

答案

  • 原理
    • 写操作时复制整个数组。
    • 读操作不需要加锁。
    • 适合读多写少的场景。
  • 缺点
    • 写操作开销大。
    • 内存占用高。
    • 数据可能不一致。

示例

java
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("value");
String value = list.get(0);

5. CopyOnWriteArraySet

问题:CopyOnWriteArraySet的实现原理是什么?

答案

  • 原理
    • 内部使用CopyOnWriteArrayList。
    • 通过addIfAbsent方法保证元素唯一。
    • 适合读多写少的场景。

示例

java
CopyOnWriteArraySet<String> set = new CopyOnWriteArraySet<>();
set.add("value");
boolean contains = set.contains("value");

6. ConcurrentLinkedQueue

问题:ConcurrentLinkedQueue的实现原理是什么?

答案

  • 原理
    • 基于链表实现。
    • 使用CAS操作保证线程安全。
    • 无界队列。
    • 不阻塞操作。

示例

java
ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
queue.offer("value");
String value = queue.poll();

7. ConcurrentLinkedDeque

问题:ConcurrentLinkedDeque的实现原理是什么?

答案

  • 原理
    • 基于双向链表实现。
    • 使用CAS操作保证线程安全。
    • 无界队列。
    • 支持双端操作。

示例

java
ConcurrentLinkedDeque<String> deque = new ConcurrentLinkedDeque<>();
deque.addFirst("value");
String value = deque.pollFirst();

8. BlockingQueue

问题:BlockingQueue有哪些实现类?

答案

  • ArrayBlockingQueue:基于数组的有界阻塞队列。
  • LinkedBlockingQueue:基于链表的可选有界阻塞队列。
  • PriorityBlockingQueue:基于优先级的无界阻塞队列。
  • DelayQueue:基于延迟时间的无界阻塞队列。
  • SynchronousQueue:不存储元素的阻塞队列。
  • LinkedTransferQueue:基于链表的无界阻塞队列。
  • LinkedBlockingDeque:基于链表的可选有界阻塞双端队列。

9. ArrayBlockingQueue

问题:ArrayBlockingQueue的特点是什么?

答案

  • 特点
    • 基于数组实现。
    • 有界队列。
    • FIFO顺序。
    • 使用ReentrantLock保证线程安全。

示例

java
BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
queue.put("value");
String value = queue.take();

10. LinkedBlockingQueue

问题:LinkedBlockingQueue的特点是什么?

答案

  • 特点
    • 基于链表实现。
    • 可选有界队列。
    • FIFO顺序。
    • 使用两把锁提高并发性能。

示例

java
BlockingQueue<String> queue = new LinkedBlockingQueue<>(10);
queue.put("value");
String value = queue.take();

11. PriorityBlockingQueue

问题:PriorityBlockingQueue的特点是什么?

答案

  • 特点
    • 基于堆实现。
    • 无界队列。
    • 按优先级排序。
    • 元素必须实现Comparable接口。

示例

java
BlockingQueue<Integer> queue = new PriorityBlockingQueue<>();
queue.put(3);
queue.put(1);
queue.put(2);
Integer value = queue.take(); // 1

12. DelayQueue

问题:DelayQueue的特点是什么?

答案

  • 特点
    • 基于PriorityBlockingQueue实现。
    • 无界队列。
    • 元素必须实现Delayed接口。
    • 只有到期元素才能被取出。

示例

java
class DelayedElement implements Delayed {
    private long delayTime;
    private long expireTime;
    
    public DelayedElement(long delayTime) {
        this.delayTime = delayTime;
        this.expireTime = System.currentTimeMillis() + delayTime;
    }
    
    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(expireTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }
    
    @Override
    public int compareTo(Delayed o) {
        return Long.compare(this.expireTime, ((DelayedElement) o).expireTime);
    }
}

BlockingQueue<DelayedElement> queue = new DelayQueue<>();
queue.put(new DelayedElement(1000));
DelayedElement element = queue.take();

13. SynchronousQueue

问题:SynchronousQueue的特点是什么?

答案

  • 特点
    • 不存储元素。
    • 每个put操作必须等待一个take操作。
    • 每个take操作必须等待一个put操作。
    • 适合传递任务。

示例

java
BlockingQueue<String> queue = new SynchronousQueue<>();
new Thread(() -> {
    try {
        queue.put("value");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}).start();

String value = queue.take();

14. BlockingDeque

问题:BlockingDeque有哪些实现类?

答案

  • LinkedBlockingDeque:基于链表的可选有界阻塞双端队列。

示例

java
BlockingDeque<String> deque = new LinkedBlockingDeque<>(10);
deque.putFirst("value");
String value = deque.takeFirst();

15. ConcurrentSkipListMap

问题:ConcurrentSkipListMap的实现原理是什么?

答案

  • 原理
    • 基于跳表实现。
    • 使用CAS操作保证线程安全。
    • 有序映射。
    • 支持高并发。

示例

java
ConcurrentSkipListMap<String, String> map = new ConcurrentSkipListMap<>();
map.put("key", "value");
String value = map.get("key");

16. ConcurrentSkipListSet

问题:ConcurrentSkipListSet的实现原理是什么?

答案

  • 原理
    • 内部使用ConcurrentSkipListMap。
    • 有序集合。
    • 支持高并发。

示例

java
ConcurrentSkipListSet<String> set = new ConcurrentSkipListSet<>();
set.add("value");
boolean contains = set.contains("value");

17. 并发集合的迭代器

问题:并发集合的迭代器有什么特点?

答案

  • 弱一致性:迭代器反映集合在迭代器创建时的状态。
  • 不抛出ConcurrentModificationException:不会在迭代过程中抛出异常。
  • 可能不一致:迭代过程中集合可能被修改。

18. 并发集合的性能

问题:如何选择并发集合?

答案

  • ConcurrentHashMap:适合高并发的Map场景。
  • CopyOnWriteArrayList:适合读多写少的List场景。
  • ConcurrentLinkedQueue:适合高并发的非阻塞队列场景。
  • ArrayBlockingQueue:适合有界阻塞队列场景。
  • LinkedBlockingQueue:适合可选有界阻塞队列场景。
  • PriorityBlockingQueue:适合优先级队列场景。
  • DelayQueue:适合延迟队列场景。
  • SynchronousQueue:适合传递任务场景。

19. 并发集合的线程安全

问题:并发集合如何保证线程安全?

答案

  • CAS操作:使用CAS操作保证原子性。
  • synchronized:使用synchronized保证同步。
  • ReentrantLock:使用ReentrantLock保证同步。
  • volatile:使用volatile保证可见性。

20. 并发集合的内存模型

问题:并发集合的内存模型是什么?

答案

  • happens-before:并发集合的操作满足happens-before关系。
  • 可见性:一个线程的修改对其他线程可见。
  • 原子性:操作是原子的。

21. 并发集合的扩容

问题:ConcurrentHashMap如何扩容?

答案

  • Java 7
    • Segment扩容:Segment中的HashEntry数组扩容。
    • Segment数量固定。
  • Java 8
    • Node数组扩容:Node数组扩容。
    • 使用多线程协助扩容。
    • 扩容时使用ForwardingNode。

22. 并发集合的统计

问题:如何统计并发集合的大小?

答案

  • size():返回集合的大概大小。
  • mappingCount():返回ConcurrentHashMap的大概映射数。
  • longSize():返回ConcurrentHashMap的精确映射数。

示例

java
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
int size = map.size();
long mappingCount = map.mappingCount();
long longSize = map.longSize();

23. 并发集合的批量操作

问题:并发集合有哪些批量操作?

答案

  • forEach:遍历集合。
  • search:搜索元素。
  • reduce:归约操作。
  • forEachKey:遍历键。
  • forEachValue:遍历值。
  • forEachEntry:遍历键值对。

示例

java
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");

// forEach
map.forEach(1, (key, value) -> System.out.println(key + ":" + value));

// search
String result = map.search(1, (key, value) -> value.equals("value1") ? key : null);

// reduce
String reduced = map.reduce(1, (key, value) -> key + ":" + value, (v1, v2) -> v1 + "," + v2);

24. 并发集合的原子操作

问题:ConcurrentHashMap有哪些原子操作?

答案

  • putIfAbsent:如果键不存在则添加。
  • remove:如果键和值匹配则删除。
  • replace:如果键存在则替换。
  • computeIfAbsent:如果键不存在则计算值。
  • computeIfPresent:如果键存在则计算值。
  • compute:计算值。
  • merge:合并值。

示例

java
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();

// putIfAbsent
map.putIfAbsent("key", "value");

// remove
map.remove("key", "value");

// replace
map.replace("key", "oldValue", "newValue");

// computeIfAbsent
map.computeIfAbsent("key", k -> "computedValue");

// computeIfPresent
map.computeIfPresent("key", (k, v) -> v + "updated");

// compute
map.compute("key", (k, v) -> v == null ? "value" : v + "updated");

// merge
map.merge("key", "value", (oldValue, newValue) -> oldValue + newValue);

25. 并发集合的最佳实践

问题:使用并发集合的最佳实践有哪些?

答案

  • 根据场景选择合适的并发集合。
  • 读多写少场景使用CopyOnWriteArrayList。
  • 高并发场景使用ConcurrentHashMap。
  • 需要阻塞时使用BlockingQueue。
  • 需要排序时使用ConcurrentSkipListMap。
  • 避免在迭代时修改集合。
  • 使用原子操作代替手动同步。
  • 使用批量操作提高性能。
  • 注意并发集合的内存开销。
  • 注意并发集合的性能特点。