一.同步容器类
同步容器类包括Vector和Hashtable。
1.同步容器类的问题
同步容器类都是线程安全的,但在某些情况下可能需要额外的客户端加锁来保护复合操作。
public class UnsafeVectorHelpers { public static Object getLast(Vector list) { int lastIndex = list.size() - 1; return list.get(lastIndex); } public static void deleteLast(Vector list) { int lastIndex = list.size() - 1; list.remove(lastIndex); }
这些方法看似没有任何问题,从某种程度上来看也确实如此——无论多少个线程同时调用它们,也不破坏Vector。但从这些方法的调用者角度来看,情况就不同了。如果线程A在包含10个元素的Vector上调用getLast,同时线程B在同一个 Vector上调用deleteLast,这些操作的交替执行,getLast 将抛出ArrayIndexOutOfBoundsException异常。在调用size与调用getLast这两个操作之间,Vector变小了,因此在调用size时得到的索引值将不再有效。这种情况很好地遵循了Vector的规范——如果请求一个不存在的元素,那么将抛出一个异常。但这并不是getLast的调用者所希望得到的结果(即使在并发修改的情况下也不希望看到),除非Vector 从一开始就是空的。
步容器类通过其自身的锁来保护它的每个方法。通过获得容器类的锁,我们可以使getLast和deleteLast成为原子操作,并确保Vector的大小在调用size和get之间不会发生变化,如下面程序所示。
public class SafeVectorHelpers { public static Object getLast(Vector list) { synchronized (list) { int lastIndex = list.size() - 1; return list.get(lastIndex); } } public static void deleteLast(Vector list) { synchronized (list) { int lastIndex = list.size() - 1; list.remove(lastIndex); } } }
2.迭代器与ConcurrentModificationException
如果在迭代期间计数器被修改,那么hasNext或next将抛出ConcurrentModificationException。
3.隐藏迭代器
容器的hashCode和equals等方法也会间接地执行迭代操作,当容器作为另一个容器的元素或键值时,就会出现这种情况。同样,containsAll、renoveAll 和retainAll等方法,以及把容器作为参数的构造函数,都会对容器进行迭代。所有这些间接的迭代操作都可能抛出ConcurrentModificationException。
二.并发容器
1.ConcurrentHashMap
与HashMap 一样,ConcurrentHashMap 也是一个基于散列的Map,但它使用了一种完全不同的加锁策略来提供更高的并发性和伸缩性。ConcurrentHashMap并不是将每个方法都在同一个锁上同步并使得每次只能有一个线程访问容器,而是使用一种粒度更细的加锁机制来实现更大程度的共享,这种机制称为分段锁。
2.额外的原子Map操作
但是,一些常见的复合操作,例如“若没有则添加”、“若相等则移除(Remove-If-Equal)”和“若相等则替换(Replace-If-Equal)”等,都已经实现为原子操作并且在ConcurrentMap 的接口中声明。
3.CopyOnWriteArrayList
同步容器类都是线程安全的,但在某些情况下可能需要额外的客户端加锁来保护复合操作。