Java高并发编程之同步容器

同步容器类包括两部分,一个是 Vector和 HashTable,是早期JDK的一部分。另一个是它们的同系容器,在JDK1.2中才被加入的同步包装(wrapper)类. 由Collections.synchronizedXxx工厂方法创建的。这些类通过封装他们的状态,并对每一个公共方法进行同步而实现了线程安全,这样一次只有一个线程访问容器状态。但对于复合操作,任然需要加锁进行保护。

这两个容器的实现和早期的ArrayList和HashMap代码实现基本一样,不同在于Vector和HashTable在每个方法上都添加了synchronized关键字来保证同一个实例同时只有一个线程能访问,部分源码如下:

//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) {};

1
2
3
4
5
6
7
8

通过对每个方法添加synchronized,保证了多次操作的串行。这种方式虽然使用起来方便了,但并没有解决高并发下的性能问题,与手动锁住ArrayList和HashMap并没有什么区别,不论读还是写都会锁住整个容器。其次这种方式存在另一个问题:当多个线程进行复合操作时,是线程不安全的。可以通过下面的代码来说明这个问题:

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

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

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

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

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

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

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

上次更新: 2020/07/29, 14:07:00
最近更新
01
RabbitMQ简介
10-27
02
聊聊Java多态
10-21
03
JVM垃圾回收器
10-16
更多文章>