一、包装线程不安全的集合
ArrayList
、LinkedList
、HashSet
、TreeSet
、HashMap
、TreeMap
等都是线程不安全的。当多个并发线程向这些集合中存、取元素时,就可能破坏这些集合的数据完整性。
如果程序中有多个线程可能访问以上这些集合,就可以使用Collections提供的类方法把这些集合包装成线程安全的集合。
返回值 | 方法名 | 描述 |
---|---|---|
static <T> Collection<T> | synchronizedCollection(Collection<T> c) | 返回指定 collection 支持的同步(线程安全的)collection。 |
static<T> List<T> | synchronizedList(List<T> list) | 返回指定列表支持的同步(线程安全的)列表。 |
static<K,V> Map<K,V> | synchronizedMap(Map<K,V> m) | 返回由指定映射支持的同步(线程安全的)映射 |
static<T> Set<T> | synchronizedSet(Set<T> s) | 返回指定 set 支持的同步(线程安全的)set |
static<K,V> SortedMap<K,V> | synchronizedSortedMap(SortedMap<K,V> m) | 返回指定有序映射支持的同步(线程安全的)有序映射。 |
如果像将传统的集合变成安全的,可以到Collections中找对应的静态方法。
主要使用代理的方式实现安全(具体可以查看源码),通过一个内部类。
private static class SynchronizedMap<K,V>
implements Map<K,V>, Serializable {
private static final long serialVersionUID = 1978198479659022715L;
private final Map<K,V> m; // Backing Map
final Object mutex; // Object on which to synchronize
//所有的方法都需要使用到mutex。 所以在多线程的情况下,使用
//这种方式去进行同步效率还是比较低的。
SynchronizedMap(Map<K,V> m) {
this.m = Objects.requireNonNull(m);
mutex = this;
}
SynchronizedMap(Map<K,V> m, Object mutex) {
this.m = m;
this.mutex = mutex;
}
public int size() {
synchronized (mutex) {return m.size();}
}
……
}
但是,这些集合通常效率不是很高。
二、线程安全的集合类
从java5 开始,在java.util.concurren
t包下,提供了大量支持高效并发访问的集合接口和实现类
这些线程安全的集合类可大致分为两类。以Concurrent
开头的集合类,和 以CopyOnWrite
开头的集合类。
以Concurrent
开头的集合类采用了更加复杂的算法来保证永远不会锁住整个集合,因此在并发写入时有较好的性能。
在默认情况下,ConcurrentHashMap
支持16
个线程并发写入,当超过16个线程并发向该Map写入时,可能有一些线程需要等待,实际上,可通过concurrencyLevel
构造参数来支持更多并发写入线程。
与HashMap等普通集合不同,
ConcurrentLinkedQueue
和ConcurrentHashMap
支持多线程并发访问,所以当使用迭代器来遍历集合元素时,该迭代器可能不能反映出创建迭代器之后所做的修改,但程序不会抛出任何异常。
CopyOnWriteArrayList
集合。它采用了复制底层数组的方式来实现写操作。由于执行写入操作时需要频繁地复制数组,性能比较差。因此CopyOnWriteArrayList
适合在读取操作远大于写入操作的场景,例如缓存等。
部分工具类描述
类 | 描述 |
---|---|
ConcurrentHashMap | 一个高效的并发HashMap |
CopyOnWriteArrayList | 在读多写少的场合,这个List性能非常好,远远好于Vector |
ConcurrentLinkedQueue | 高效的并发队列,使用链表实现。 可以看做一个线程安全的LinkedList |
BlockiingQueue | 是一个接口,非常适合用于做数据共享的通道 |
ConcurrentSkipListMap | 跳表的实现。是一个Map,使用跳表的数据结构进行快速查找 |
三、案例
每个段都有独立的锁。实现了并行,提高了效率; 在1.8以后又采用 了CAS
算法去实现同步
3.1 java.util.Collections包中的并发类
使用Collections包中的并发类,去实现同步,但是多线程并发执行修改数据,抛出异常:并发修改异常
package test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class TestCopyOnWriteArrayList {
public static void main(String[] args) {
HelloThread ht = new HelloThread();
for (int i = 0; i < 10; i++) {
new Thread(ht).start();
}
}
}
class HelloThread implements Runnable{
private static List<String> list = Collections.synchronizedList(new ArrayList<String>());
static{
list.add("AA");
list.add("BB");
list.add("CC");
}
@Override
public void run() {
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
list.add("AA");
}
}
}
运行结果
3.2 java.util.concurrent包中的并发类
CopyOnWriteArrayList/CopyOnWriteArraySet
: 写入并复制
注意: 添加操作多时,效率低,因为每次添加时都会进行复制,开销非常的大。并发迭代操作多时可以选择
package test;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
public class TestCopyOnWriteArrayList {
public static void main(String[] args) {
HelloThread ht = new HelloThread();
for (int i = 0; i < 10; i++) {
new Thread(ht).start();
}
}
}
class HelloThread implements Runnable{
private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
static{
list.add("AA");
list.add("BB");
list.add("CC");
}
@Override
public void run() {
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
list.add("AA");
}
}
}
运行结果正常。
参考
软件包 java.util.concurrent 的描述 http://tool.oschina.net/apidocs/apidoc?api=jdk-zh