Java核心技术卷I:基础知识(原书第8版):14.7 线程安全的集合

本文介绍了Java中多种线程安全的集合实现,包括ConcurrentHashMap、ConcurrentSkipListMap等高效映射、集合及队列;CopyOnWriteArrayList和CopyOnWriteArraySet等写时复制的线程安全集合;以及Vector和Hashtable等旧版线程安全集合。

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

铁文整理

14.7 线程安全的集合

    如果多线程要并发地修改一个数据结构,例如散列表,那么很容易会破坏这个数据结构(有关散列表的详细信息见第13章)。例如,一个线程可能要开始向表中插入一个新元素。假定在调整散列表各个桶之间的链接关系的过程中,被剥夺了控制权。如果另一个线程也开始遍历同一个链表,可能使用无效的链接并造成混乱,会抛出异常或者陷入死循环。

    可以通过提供锁来保护共享数据结构,但是选择线程安全的实现作为替代可能更容易些。当然,前一节讨论的阻塞队列就是线程安全的集合。在下面各小节中,将讨论Java类库提供的另一种线程安全的集合。

14.7.1 高效的映像、集合和队列

    java.util.concurrent包提供了映像、有序集和队列的高效实现:ConcurrentHashMapConcurrentSkipListMapConcurrentSkipListSetConcurrentLinkedQueue

    这些集合使用复杂的算法,通过允许并发地访问数据结构的不同部分来使竞争极小化。

    与大多数集合不同,size方法不必在常量时间内操作。确定这样的集合当前的大小通常需要遍历。

    集合返回弱一致性的迭代器,这意味着迭代器不一定能反映出它们被构造之后的所有的修改,但是,它们不会将同一个值返回两次,也不会抛出CocurrentModificationException异常。

    注释:与之形成对照的是,集合如果在迭代器构建之后发生改变,java.util包中的迭代器将抛出一个ConcurrentModificationException异常。

    并发的散列映像表,可高效地支持大量的读者和一定数量的写者。默认情况下,假定可以有多达16个写者线程同时执行。可以有更多的写者线程,但是,如果同一时间多于16个,其他线程将暂时被阻塞。可以指定更大数目的构造器,然而,恐怕没有这种必要。

    ConcurrentHashMapConcurrentSkipListMap类有相应的方法用于原子性的关联插入以及关联删除。putIfAbsent方法自动地添加新的关联,前提是原来没有这一关联。对于多线程访问的缓存来说这是很有用的,确保只有一个线程向缓存添加项:

    cache.putAbsent(key, value);

    相反的操作是删除(或许应该叫作removeIfPresend)。调用

    cache.remove(key, value)

    将原子性地删除键值对,如果它们在映像表中出现的话。最后,

    cache.replace(key, oldValue, newValue)

    原子性地用新值替换旧值,假定旧值与指定的键值关联。

APIjava.util.concurrent.ConcurrentLinkedQueue<E> 5.0

  • ConcurrentLinkedQueue<E>():构造一个可以被多线程安全访问的无边界非阻塞的队列。

APIjava.util.concurrent.ConcurrentLinkedQueue<E> 6

  • ConcurrentLinkedQueue<E>()

  • ConcurrentLinkedQueue<E>(Comparator<? super E> comp):构造一个可以被多线程安全访问的有序集。第一个构造器要求元素实现Comparable接口。

APIjava.util.concurrent.ConcurrentHashMap<K, V> 5.0

  APIjava.util.concurrent.ConcurrentSkipListMap<K, V> 6

  • ConcurrentHashMap<K, V>()

  • ConcurrentHashMap<K, V>(int initialCapacity)

  • ConcurrentHashMap<K, V>(int initialCapacity, float loadFactor, int concurrencyLevel):构造一个可以被多线程安全访问的散列映像表。参数:initialCapacity:集合的初始容量,默认值为16。loadFactor:控制调整:如果每一个桶的平均负载超过这个因子,表的大小会被重新调整。默认值为0.75。concurrencyLevel:并发写者线程的估计数目。

  • ConcurrentSkipListMap<K, V>()

  • ConcurrentSkipListSet<K, V>(Comparator<? super K> comp):构造一个可以被多线程安全访问的有序的映像表。第一个构造器要求键实现Comparable接口。

  • V putIfAbsent(K key, V value):如果该键没有在映像表中出现,则将给定的值同给定的键关联起来,并返回null。否则返回与该键关联的现有值。

  • boolean remove(K key, V value):如果给定的键与给定的值关联,删除给定的键与值并返回真。否则,返回false。

  • boolean replace(K key, V oldValue, V newValue):如果给定的键当前与oldValue相关联,用它与newValue关联。否则,返回false。

14.7.2 写数组的拷贝

    CopyOnWriteArrayListCopyOnWriteArraySet是线程安全的集合,其中所有的修改线程对底层数组进行复制。如果在集合上进行迭代的线程数超过修改线程数,这样的安排是很有用的。当构建一个迭代器的时候,它包含一个对当前数组的引用。如果数组后来被修改了,迭代器仍然引用旧数组,但是,集合的数组已经被替换了。因而。旧的迭代器拥有一致的(可能过时的)视图,访问它无须任何同步开销。

14.7.3 旧的线程安全的集合

    Java的初始版本开始,VectorHashtable类就提供了线程安全的动态数组和散列表的实现。在Java SE 1.2中,这些类被弃用了,取而代之的是ArrayListHashMap类。这些类不是线程安全的,而集合库中提供了不同的机制。任何集合类通过使用同步包装器变成线程安全的:

        List<E> synchArrayList = Collections.synchronizedList(new ArrayList<E>());

        Map<K, V> synchHashMap = Collections.synchronizedMap(new HashMap<K, V>());

    结果集合的方法使用锁加以保护,提供了线程的安全访问。

    应该确保没有任何线程通过原始的非同步方法访问数据结构。最便利的方法是确保不保存任何指向原始对象的引用,简单地构造一个集合并立即传递给包装器。像我们的例子中所做的那样。

    如果在另一个线程可能进行修改时要对集合进行迭代,仍然需要使用“客户端”封锁:

        synchronized (synchHashMap) {

            Iterator<K> iter = synchHashMap.keySet().iterator();

            while (iter.hasHext()) ...;

        }

    如果使用“for each”循环必须使用同样的代码,因为循环使用了迭代器。注意:如果在迭代过程中,别的线程修改集合,迭代器会失效,抛出ConcurrentModifucationException异常。同步仍然是需要的,因此并发的修改可以被可靠地检测出来。

    最好使用java.util.concurrent包中定义的集合,不使用同步包装器中的。特别是,假如它们访问的是不同的桶,由于ConcurrentHashMap已经精心地实现了,多线程可以访问它而且不会彼此阻塞。有一个例外是经常被修改的数组列表。在那种情况下,同步的ArrayList可以胜过CopyOnWriteArrayList

APIjava.util.Collections 1.2

  • static <E> Collection<E> synchronizedCollection(Collection<E> c)

  • static <E> List synchronizedList(List<E> c)

  • static <E> Set synchronizedSet(Set<E> c)

  • static <E> SortedSet synchronizedSortedSet(SortedSet<E> c)

  • static <Ky V> Map<K, V> synchronizedMap(Map<K, V> c)

  • static <K, V> SortedMap<K, V> synchronizedSortedMap(SortedMap<K, V> c):构建集合视图,该集合的方法是同步的。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值