Java并发面试题整理(JUC、集合与并发)(附答案,持续更新)

1,097 阅读5分钟

这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战

Java并发已经是Java面试必问的一块内容,对这块知道越多的细节,就越容易让面试官刮目相看。

我结合自身学习和面试经历,总结了Java并发的相关面试题,包括线程基础、多线程与并发、线程安全、线程池、锁、内存模型、JUC、集合与并发这几块内容。

因篇幅限制,分成三篇文章

上篇:多线程与并发,线程安全,线程池

中篇:内存模型、锁

下篇:JUC、集合与并发

JUC篇

对Java并发包有没有了解?

Java并发包指的是 java.util.concurrent(简称 JUC)包和其子包下的类和接口,为并发提供了各种功能支 持,比如:

  • 锁机制类 Locks : Lock, Condition, ReadWriteLock
  • 原子操作类Atomic : AtomicInteger
  • 线程池相关类Executer : Future, Callable, Executor
  • 信号量三组工具类Tools : CountDownLatch, CyclicBarrier, Semaphore
  • 并发集合类Collections : CopyOnWriteArrayList, ConcurrentMap

常用的原子操作类有哪些?

  • AtomicBoolean
  • AtomicInteger
  • AtomicLong
  • LongAdder
  • AtomicReference
  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicReferenceArray

原子操作类的底层实现原理是什么?

无锁技术,内部调用 Unsafe API中的CAS(Compare and Swap)方法:

  • Unsafe API - Compare-And-Swap
  • CPU硬件指令支持: CAS指令

两个要点:

  1. volatile的value变量保证可见性
  2. CAS操作保证写入不冲突

LongAdder 相比 AtomicLong有哪些改进,其实现原理是什么

采用了分段思想,支持更高的并发。

LongAdder extends Striped64;
transient volatile Cell[] cells;

public long sum() {
    Cell[] cs = cells;
    long sum = base;
    if (cs != null) {
        for (Cell c : cs)
            if (c != null)
                sum += c.value;
    }
    return sum;
}

Semaphore 是什么? 与锁有什么区别?

Semaphore 即信号量, 是一个计数信号,即允许N个许可。

  • acquire() 方法,阻塞方式获取一个许可。
  • release() 方法,释放一个许可。

如果信号量=1, 则等价于互斥锁。

如果信号量>1, 相当于共享锁。

用过 CountDownLatch 吗?

CountDownLatch(闭锁)可以看作一个只能做减法的计数器,可以让一个或多个线程等待执行。

场景: Master 线程等待 Worker 线程把任务执行完

示例:

  • 等所有人干完手上的活,包工头宣布下班休息。
  • 吃酒席: 大家围成一桌, 等剩下的座位数归0, 服务员才上菜。

重要方法:

public CountDownLatch(int count) // 构造方法(总数)
void await() throws InterruptedException // 阻塞并等待数量归0
boolean await(long timeout, TimeUnit unit) // 限时等待
void countDown() // 等待数减1
long getCount() // 返回剩余数量

特点:

  1. 采用减法计数,
  2. 各个子线程内countdown,
  3. 调用线程/主线程里await,作为聚合点,一直到计数为0

你对 CyclicBarrier 有什么了解?

CyclicBarrier(循环屏障), 可以让一组线程等待满足某个条件后同时执行。

CyclicBarrier 默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用 await() 方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞,直到 parties 个线程到达,结束阻塞。

使用场景: 任务执行到一定阶段, 等待其他任务对齐

示例:

  • 组团去旅游, 到一个景点需要点名报数, 等人员到齐了才一起进场; 离开一个景点时也需要报数, 所 有人到齐之后才前往下一个景点。
  • 吃酒席: 大家围成一桌, 满10人才开吃。

特点:

  1. 采用加法计数;
  2. 各个子线程内await,与主线程无关;
  3. 可以给CyclicBarrier加一个回调作为聚合点,此回调由前面的多个线程中的某个执行;
  4. 可以复用CyclicBarrier

用过 CompletableFuture 吗?跟Future有什么区别?

Future接口可以构建异步应用,但依然有其局限性。它很难直接表述多个Future 结果之间的依赖性。 实际开发中,我们经常需要达成以下目的:

  1. 将多个异步计算的结果合并成一个
  2. 等待Future集合中的所有任务都完成
  3. Future完成事件(即,任务完成以后触发执行动作)

在Java8中,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的 复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合 CompletableFuture 的方法。

它可能代表一个明确完成的Future,也有可能代表一个完成阶段( CompletionStage ),它支持在计 算完成以后触发一些函数或执行某些动作。它实现了Future和CompletionStage接口。

什么是 AQS?他的基本原理是什么?

AQS(AbstractQueuedSynchronizer) 是一个用来构建锁和同步的框架。

  • Sync extends AbstractQueuedSynchronizer
  • 抽象队列式的同步器
  • Lock的底层实现原理两种资源共享方式: 独占 | 共享
  • 子类负责实现公平 or 非公平

Java中各种常见的锁, 例如 ReentrantLock、ReadWriteLock,以及 Semaphore、CountDownLatch 等 等,都是基于 AQS 来构建的。

AQS 在内部定义了一个 volatile int state 变量,表示同步状态:

当线程调用 lock 方法时:

  • 如果 state=0,说明没有任何线程占有这个锁,可以获得锁并将 state=1;
  • 如果 state=1,则说明有线程目前正在使用共享变量,其他线程必须加入同步队列进行等待。

用过 Condition 吗?

类比: Object monitor, wait/notify

示例:

final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();

集合与并发篇

经常使用哪些并发集合类?

  • ConcurrentHashMap
  • CopyOnWriteArrayList
  • ConcurrentLinkedQueue
  • ConcurrentLinkedDeque
  • LinkedBlockingQueue
  • DelayQueue

有哪些线程安全的集合类是有性能问题的?

  • Collections 的 synchronizedList 等工具方法
  • Vector
  • Hashtable
  • Stack

ConcurrentHashMap 有什么优势,内部实现原理是什么?

  1. JDK7的实现: 内部采用了分段锁的概念。
  2. JDK8的实现: 基于CAS、红黑树实现。

在合理hash的情况下, ConcurrentHashMap 的并发性能提升了很多倍。

ConcurrentHashMap 与 HashMap 相比有哪些不同?

  • ConcurrentHashMap 是线程安全的
  • HashMap 不是线程安全的
  • ConcurrentHashMap 的 key 和 value 都不能为 null

谈谈你对 HashMap 的理解?

什么是哈希Hash?

重写 hashCode 与 equals 有什么注意的地方?

HashMap有什么并发安全问题