Java集合框架的容器类绝大部分不是线程安全的,仅有的线程安全实现,比如Vector、Stack,在性能方面也远不尽如人意。幸好Java语言提供了并发包(java.util.concurrent),为高度并发需求提供了更加全面的工具支持。

Java提供了不同层面的线程安全支持。在传统集合框架内部,除了Hashtable等同步容器,还提供了所谓的同步包装器,我们可以调用Collections工具类提供的包装方法,来获取一个同步的包装容器(如Collections.synchronizedMap),但是它们都是利用非常粗粒度的同步方式,在高并发情况下,性能比较低下。

更加普遍的选择是利用并发包提供的线程安全容器类,包括:

  • 各种并发容器,比如ConcurrentHashMap、CopyOnWriteArrayList。
  • 各种线程安全队列(Queue、Deque),如ArrayBlockingQueue、SynchronousQueue。
  • 各种有序容器的线程安全版本等。

保证线程安全的方式,包括从简单的synchronize,到基于更加精细化的分离锁。具体选择要看开发的场景需求,总体来说,并发包内提供的容器通用场景,远优于早期的简单同步实现。

知识扩展

  • 为什么需要ConcurrentHashMap?
    • Hashtable本身比较低效,因为它的实现基本就是在各种方法上加synchronized关键字。简单来说,这就导致了所有并发操作都要竞争同一把锁,一个线程在进行同步操作时,其他线程只能等待,大大降低了并发操作的效率。而Collections提供的同步包装器也并没有实质性的改变。
    • ConcurrentHashMap能大大提高并发效率。它一直在不断的演化,比如在Java8中就发生了很大的变化。早期,其实现基于分离锁、volatile、Unsafe、重试机制等,有效避免了类似Hashtable整体同步的问题,大大提高了性能。
    • Java8和之后的版本中,ConcurrentHashMap发生的变化包括:
      • 同步的粒度更细致。
      • 其内部仍然有Segment定义,但仅仅是为了保证序列化时的兼容性而已,不再有任何结构上的用处。
      • 简化初始化操作,修改为lazy-load形式。
      • 数据存储利用volatile来保证可见性。
      • 使用CAS等操作,在特定场景下实现无锁并发。
      • 使用Unsafe、LongAdder之类底层手段,进行极端情况的优化。