Java 容器--同步容器和并发容器

本文介绍了Java中同步容器如Vector、Stack和HashTable的特点,以及Collections类如何创建线程安全容器。同时,讨论了同步容器的性能问题和安全缺陷,并引出并发容器的概念,重点列举了juc包中的几个重要并发容器及其特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

同步容器

Vector、Stack、HashTable

Collections 类中提供的静态工厂方法创建的类

并发容器


Java 中的容器主要可以分为四个大类,分别是 ListMapSet 和 Queue,但并不是所有的 Java 容器都是线程安全的。说到线程安全的问题,我们先来理解两个名词 同步容器 和 并发容器

同步容器

在 Java 中,同步容器主要包括 2 类:

VectorStackHashTable

  • Vector 实现了 List 接口,Vector 实际上就是一个数组,和 ArrayList 类似,但是 Vector 中的方法都是 synchronized 方法,即进行了同步措施。
  • Stack 也是一个同步容器,它的方法也用 synchronized 进行了同步,它实际上是继承于 Vector 类。
  • HashTable 实现了 Map 接口,它和 HashMap 很相似,但是 HashTable 进行了同步处理,而 HashMap 没有。

部分源码如下:

1
2
3
4
5
6
7
//Vector
public synchronized int size() {};
public synchronized E get(int index) {};

//HashTable 
public synchronized V put(K key, V value) {};
public synchronized V remove(Object key) {};

Collections 类中提供的静态工厂方法创建的类

由 Collections.synchronizedXxxx 等方法,来实现容器的同步。比如下面的示例代码中,分别把 ArrayListHashSet 和 HashMap 包装成了线程安全的 ListSet 和 Map

1
2
3
4
5
List list = Collections.synchronizedList(new ArrayList());

Set set = Collections.synchronizedSet(new HashSet());

Map map = Collections.synchronizedMap(new HashMap());

同步容器的缺陷

  • 性能问题:由于被 synchronized 修饰的方法,每次只允许一个线程执行,其他试图访问这个方法的线程只能等待。显然,这种方式比没有使用 synchronized 的容器性能要差。

  • 安全问题:同步容器真的一定安全吗?未必。同步容器未必真的安全。在做复合操作时,仍然需要加锁来保护。

可以通过下面的代码来说明这个问题:

1
2
3
4
public static void deleteVector(){
    int index = vectors.size() - 1;
    vectors.remove(index);
}

代码中对 Vector 进行了两步操作,首先获取 size,然后移除最后一个元素,多线程情况下如果两个线程交叉执行,A 线程调用 size 后,B 线程移除最后一个元素,这时 A 线程继续 remove 将会抛出索引超出的错误。

那么怎么解决这个问题呢?最直接的修改方案就是对代码块加锁来防止多线程同时执行:

1
2
3
4
5
6
public static void deleteVector(){
    synchronized (vectors) {
        int index = vectors.size() - 1;
        vectors.remove(index);
    }
}

如果上面的问题通过加锁来解决没有太直观的影响,那么来看看对 vectors 进行迭代的情况:

1
2
3
4
5
6
7
public static void foreachVector(){
    synchronized (vectors) {
        for (int i = 0; i < vectors.size(); i++) {
            System.out.println(vectors.get(i).toString());
        }
    }
}

为了避免多线程情况下在迭代的过程中其他线程对 vectors 进行了修改,就不得不对整个迭代过程加锁,想象这么一个场景,如果迭代操作非常频繁,或者 vectors 元素很大,那么所有的修改和读取操作将不得不在锁外等待,这将会对多线程性能造成极大的影响。那么有没有什么方式能够很好的对容器的迭代操作和修改操作进行分离,在修改时不影响容器的迭代操作呢?这就需要 java.util.concurrent 包中的各种并发容器了出场了。

案例:

1
2
3
4
5
6
7
8
9
public class _03_T_SynchronizedList {
    public static void main(String[] args) {
        //未加锁
        List<String> list = new ArrayList<>();

        //已加锁
        List<String> synchronizedList = Collections.synchronizedList(list);
    }
}

并发容器

Java 在 1.5 版本之前所谓的线程安全的容器,主要指的就是 同步容器,当然因为所有方法都用 synchronized 来保证互斥,串行度太高了,性能太差了。因此 Java 在 1.5 及之后版本提供了性能更高的容器,我们一般称为 并发容器

并发容器虽然数量非常多,但依然是前面我们提到的四大类:ListMapSet 和 Queue

并发容器关系图。

我们先来简单的了解下,JDK 的 java.util.concurrent 包(即 juc)中提供了几个非常有用的 并发容器

  • CopyOnWriteArrayList - 线程安全的 ArrayList
  • CopyOnWriteArraySet - 线程安全的 Set,它内部包含了一个 CopyOnWriteArrayList,因此本质上是由 CopyOnWriteArrayList 实现的。
  • ConcurrentSkipListSet - 相当于线程安全的 TreeSet。它是有序的 Set。它由 ConcurrentSkipListMap 实现。
  • ConcurrentHashMap - 线程安全的 HashMap。采用分段锁实现高效并发。
  • ConcurrentSkipListMap - 线程安全的有序 Map。使用跳表实现高效并发。
  • ConcurrentLinkedQueue - 线程安全的无界队列。底层采用单链表。支持 FIFO
  • ConcurrentLinkedDeque - 线程安全的无界双端队列。底层采用双向链表。支持 FIFO 和 FILO
  • ArrayBlockingQueue - 数组实现的阻塞队列。
  • LinkedBlockingQueue - 链表实现的阻塞队列。
  • LinkedBlockingDeque - 双向链表实现的双端阻塞队列。

后续我们再来具体的学习并发容器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清风絮柳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值