一:获取线程安全的集合
早期JDK中除了vector和hashtable是线程安全的,Set接口的实现类还没有一个线程安全的Set。
我们通过下面的方法获取到线程安全的Set。
Collections中的工具方法
Collections 工具类中提供了多个可以获得线程安全集合的方法。(JDK1.5以前)
• public static <T> Collection<T> synchronizedCollection(Collection<T> c)
• public static <T> List<T> synchronizedList(List<T> list)
• public static <T> Set<T> synchronizedSet(Set<T> s)
• public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
• public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s)
• public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)
以ArrayList为例
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
List<Integer> safeList = Collections.synchronizedList(list);//线程安全的List(互斥)
}
源代码
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
Collections内的静态内部类static class SynchronizedRandomAccessList<E>
部分源代码
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
JDK1.2 提供,接口统一、维护性高,但性能没有提升,均以 synchonized 实现。
二:CopyOnWriteArrayList
• 线程安全的 ArrayList ,加强版读写分离。
• 写有锁,读无锁,读写之间不阻塞,优于读写锁。
• 写入时,先 copy 一个容器副本、再添加新元素, 最后 替换引用。
• 使用方式与 ArrayList 无异。
写操作:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
源码分析:①加锁②getArray()获取数组、和数组长度③拷贝原数组,长度+1④添加的元素下标赋值 ⑤将新数组地址覆盖原数组中的地址⑥解锁
为什么会对读操作没有影响?
读操作:
public E get(int index) {
return get(getArray(), index);
}
源码分析:读操作得到的是一个副本,即时,读写线程同时进行,写操作没完成之前,读线程读到是原来数组的副本,而添加操作并未对原数组进行操作,只是拷贝了一份。(读写或不干扰)如果写操作完成时,再来一个读线程读取到的是新的数组的副本。
三:CopyOnWriteArraySet
• 线程安全的 Set ,底层使用 CopyOnWriteArray List 实现 。
• 唯一不同在于,使用 addIfAbsent() 添加元素,会遍历数组,
• 如存在元素,则不添加(扔掉副本)。
//addIfAbsent()源代码
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) {
// Optimize for lost race to another addXXX operation
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
源码分析:添加时,不仅完成了模版的拷贝,还对每个元素进行去重操作。如果需要添加自定类,则需要重写Object的equals方法。