首页 > 编程笔记 > Java笔记 阅读:5

Java同步容器与并发容器的区别(新手必看)

在 Java 集合框架中,主要有 List、Set、Map 和 Queue 几大类并发容器,它们的大多数实现默认都是非线程安全的,比如 ArrayList、LinkedList、HashMap 等容器都是非线程安全的。

在多线程环境中,使用这些非线程安全的容器就会出现问题,需要我们在编写程序时手动处理,非常不方便,因此 Java 提供了一些同步容器供开发者使用。

同步容器是设计用来在并发环境下安全使用的数据结构,内置了多线程操作所需的同步措施。这使得开发者能够在多线程程序中直接使用这些容器,而无须担心线程安全问题。

同步容器通常分为两种类型,分别是普通同步类和通过 Collections 类包装的内部同步类。

Java同步容器

1) 普通同步类

普通同步类有 Vector、Stack 和 HashTable,它们依靠给每个关键方法加上 synchronized 关键字来实现线程安全。

比如 Vector 容器中的 get() 和 set() 方法的源码定义如下:
public synchronized E get(int index) {
    if (index >= elementCount)
          throw new ArrayIndexOutOfBoundsException(index);
    return elementData(index);
}
public synchronized E set(int index, E element) {
    if (index >= elementCount)
          throw new ArrayIndexOutOfBoundsException(index);
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}
使用 synchronized 关键字可以使每次只有一个线程能够执行这些同步的方法,从而保证操作的原子性。

2) 内部同步类

内部同步类同步容器是通过 Collections 类创建的,通过 SynchronizedList() 和 SynchronizedSet() 方法,将原始容器包装成内部同步类 SynchronizedCollection 对象,并在关键代码块上利用 synchronized 锁定实现同步,从而创建出线程安全的容器。

SynchronizedCollection 部分源码定义如下:
static class SynchronizedCollection<E> implements Collection<E>, Serializable {
    // 内部实现细节
    public boolean add(E e) {
        synchronized (mutex) {
            return c.add(e);
        }
    }
    // 其他方法
}
从源码中可以发现,在 SynchronizedCollection 内部同步类中,通过为操作指定一个锁对象 mutex 来保证方法的线程同步。

通过对同步容器的介绍,我们发现同步容器在性能上存在不足,由于对所有操作都添加了同步锁,对于需要多步骤的复合操作,同步容器有时候并不能保证线程安全。

为了解决同步容器的这些问题,Java 提供了多种并发容器,例如 ConcurrentHashMap、CopyOnWriteArrayList 等。这些容器利用分段锁、无锁 CAS 算法等技术,将对共享资源的串行操作转换为并行操作,从而显著提升性能。

以 ConcurrentHashMap 和 HashTable 为例,虽然它们都是线程安全的 Map 集合,但是 ConcurrentHashMap 的性能比 HashTable 的性能要高,原因如下:

Java同步容器和并发容器的区别

同步容器和并发容器都是线程安全的,但是它们在设计和性能特征上有显著差异。以下是两者的主要区别。

1) 锁的粒度

2) 并发性能

3) 设计目的

4) 内存一致性影响


通过对比可知,同步容器在多线程环境中提供了基本的线程安全,但在高并发情况下可能会成为性能瓶颈。相反,并发容器通过锁的精细化和其他高级并发技术,提供了更好的性能和可伸缩性。

在实际应用中,应根据实际场景选择合适的容器类型。

相关文章