Appearance
并发集合面试题
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(); // 112. 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。
- 避免在迭代时修改集合。
- 使用原子操作代替手动同步。
- 使用批量操作提高性能。
- 注意并发集合的内存开销。
- 注意并发集合的性能特点。
