1、不安全的Set
上代码:
public static void main(String[] args) throws Exception {
Set<String> set = new HashSet<>();
for (int i = 0;i<30;i++){
new Thread(()->{
set.add(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName()+" "+set);
}).start();
}
}
//运行结果如下:多线程共同修改set集合,造成了并发修改异常
java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1445)
at java.util.HashMap$KeyIterator.next(HashMap.java:1469)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.lang.StringBuilder.append(StringBuilder.java:131)
at juc.ContainerNotSafeDemo.lambda$main$0(ContainerNotSafeDemo.java:17)
at java.lang.Thread.run(Thread.java:748)
2、安全的解决方式
使用CopyOnWriteArraySet解决
public static void main(String[] args) throws Exception {
Set<String> set = new CopyOnWriteArraySet<>();//new HashSet<>();
for (int i = 0;i<30;i++){
new Thread(()->{
set.add(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName()+" "+set);
}).start();
}
}
关于写时复制技术,在这篇博客里写过,不再赘述。深入探索一下,看源码:
public class CopyOnWriteArraySet<E> extends AbstractSet<E>
implements java.io.Serializable {
private static final long serialVersionUID = 5457747651344034263L;
private final CopyOnWriteArrayList<E> al;
/**
* Creates an empty set.
*/
public CopyOnWriteArraySet() { //无参构造方法
al = new CopyOnWriteArrayList<E>(); //实际上创建的是CopyOnWriteArrayList
}
....
}
3、关于HashSet的补充
HashSet底层是什么?看源码:
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
注释的意思是:创建一个空的HashMap,初始容量是16,负载因子是0.75.
HashSet在add的时候传的是一个值并不是<k,v>,比如下边:
new HashSet<>().add("hello");
确定HashSet底层是HashMap吗???
这是一个容易被忽略的地方,查看HashSet的add方法源码:
/**
* Adds the specified element to this set if it is not already present.
* 如果指定的元素尚未出现,则将其添加到此集合。
* More formally, adds the specified element e to this set if
* this set contains no element e2 such that
* (e==null?e2==null:e.equals(e2)).
* If this set already contains the element, the call leaves the set
* unchanged and returns false
* 如果该集合已经包含元素,则调用将不修改集合,返回false。
* @param e element to be added to this set
* @return <tt>true</tt> if this set did not already contain the specified
* element
*/
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
可以看到,add方法其实是给HashMap添加了Key,value是PRESENT,它是一个Object类型的常量。调用的还是map的add方法。