如果多线程要并发的修改一个数据结构,例如散列表,那么很容易会破坏这个数据结构。一个线程可能要开始向表中插入一个新元素。假定在调整散列表各个桶之间的链接关系的过程中,被剥夺了控制权。如果另一个线程也开始遍历同一个链表,可能使用无效的链接并造成混乱,会抛出异常或者陷入死循环。
可以通过提供锁来保护共享数据结构,但是选择线程安全的实现作为替代可能更容易些。上一篇讨论的阻塞队列就是线程安全的集合。接下来讨论java类库提供的另外一些线程安全的集合。
高效的映射表、集合和队列
java.util.concurrent包提供了映射表、有序集合和队列的高效实现:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet和ConcurrentLinkedQueue。
java.util.concurrent.ConcurrentLinkedQueue<E>
- ConcurrentLinkedQueue<E>() 构造一个可以被多线程安全访问的无边界非阻塞的队列。
- ConcurrentSkipListSet<E>(Comparateor<? super E> comp) 构造一个可以被多线程安全访问的有序集
java.util.concurrent.ConcurrentSkipListMap<K,V>
- 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
写数组的拷贝
CopyOnWriteArrayList和CopyOnWriteArraySet是线程安全的集合。其中所有的修改线程对底层数组进行复制。如果在集合上进行迭代的线程数超过修改线程数,这样的安排是很有用的。当构建一个迭代器的时候,它包含一个对当前数组的引用。如果数组后来被修改了,迭代器仍然引用旧数组,但是,集合的数组已经被替换了。因为旧的迭代器拥有一致的视图,访问无须任何同步开销。