一、HashSet
HashSet是基于HashMap实现的,因为HashMap本身是线程不安全的,所以HashMap就是线程不安全的,
简单看下HashSet的源码
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
//真正存元素的map,key是要存进set的元素,value是Object
private transient HashMap<E,Object> map;
public boolean add(E e) {
//添加元素时往内部的map中放入
return map.put(e, PRESENT)==null;
}
}
二、线程安全的set
2.1 java.util.Collections#synchronizedSet(java.util.Set)
这个工具方法接收一个set集合,对它进行包装,让每个方法都被Synchronized修饰,用这种方式保证线程安全。
因为锁对象是集合对象本身,所以存和取的方法不能同时执行,性能较低。
2.2 CopyOnWriteArraySet
这个set基于CopyOnWriteArrayList实现,保证线程安全
public class CopyOnWriteArraySet<E> extends AbstractSet<E>
implements java.io.Serializable {
private static final long serialVersionUID = 5457747651344034263L;
//内部持有一个CopyOnWriteArrayList
private final CopyOnWriteArrayList<E> al;
//添加方法,调用的是CopyOnWriteArrayList的方法
public boolean add(E e) {
return al.addIfAbsent(e);
}
}
再继续看下CopyOnWriteArrayList#addIfAbsent方法,这个方法一定是线程安全的
public boolean addIfAbsent(E e) {
//先获取存元素的数组
Object[] snapshot = getArray();
//查找当前集合中是否有e
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
//添加
addIfAbsent(e, snapshot);
}
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循环是一种针对判断效率的优化,如果在0-common中能找到e就返回false
//因为这个snapshot在调用当前方法前已经检查过了,所以循环中current[i] = snapshot[i]的时候
// snapshot[i]一定不等于e
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
//查询current中的剩余元素
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();
}
}