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()+"\t"+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()+"\t"+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方法。