HashSet的不安全问题
- HashSet与ArrayList一样也存在不安全的问题,当多线程时也会出现ConcurrentModificationException,并发修改异常
- 需要提出解决方案
问题
public static void main(String[] args) {
Set<Integer> set = new HashSet<>();
for (int i = 0; i < 30;i++) {
int finalI = i;
new Thread(()->{
set.add(finalI);
System.out.println(set);
},String.valueOf(i)).start();
}
}
解决方案
Set set = Collections.synchronizedSet(new HashSet());
- 这种方式是使用工具类的方式,而不是使用JUC下的内容
Set set = new CopyOnWriteArraySet<>();
-
与ArryList类似
-
看看CopyOnWriteArraySet的add方法
-
private final CopyOnWriteArrayList<E> al; /** * Creates an empty set. */ public CopyOnWriteArraySet() { al = new CopyOnWriteArrayList<E>(); } public boolean add(E e) { return al.addIfAbsent(e); }
-
public boolean addIfAbsent(E e) { Object[] snapshot = getArray(); return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false : addIfAbsent(e, snapshot); } /** * A version of addIfAbsent using the strong hint that given * recent snapshot does not contain e. */ 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(); } }
-
从上述可以看出
-
当创建一个CopyOnwriteArraySet时,其实就是创建了一个ArrayList
-
因为addIfAbsent这个类用来重入锁ReetrantLock,所以他可以实现同步,并发执行
-
-
为什么可以使用
聊聊HashSet的底层
- 是通过Map实现的,Map的key是唯一的,所以将Map的key作为HashSet的值,这样就可以实现唯一性质
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
HashMap的不安全问题
- HashMap与上述几个一样,也存在不安全的问题
HashMap<String, Integer> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(()->{
map.put(String.valueOf(finalI)+"==>", finalI);
},String.valueOf(i)).start();
System.out.println(map);
}
- 会出现ConcurrentModificationException,并发修改异常
解决方案
Map<String, Integer> map = Collections.synchronizedMap(new HashMap<String, Integer>());
Map<String, Integer> map = new ConcurrentHashMap<>();
- 上述是类之间的关系
HashMap的底层是什么样子的?
HashMap 的底层实现基于哈希表(Hash Table)结构,并结合了 数组 和 链表 或红黑树
- 数组:
HashMap
内部维护一个数组(通常称为“桶”或“槽”),数组的每个元素都是一个链表或红黑树的根节点。数组的大小通常是 2 的幂次方,这是为了优化哈希运算中的位运算。 - 哈希函数:当向
HashMap
中添加元素时,会使用哈希函数计算键(Key)的哈希值。这个哈希值会被用来确定元素应该存储在哪个桶中。通常,哈希值会对数组的长度取模(hash % array.length
),以确定桶的索引 - 链表或红黑树:当两个或更多的键具有相同的哈希值时(这称为哈希冲突),它们会被存储在同一个桶中的链表里。在 Java 8 中,当链表长度超过一定阈值(默认为 8)时,链表会转换为红黑树,以提高查询效率。当树的大小小于一定阈值(默认为 6)时,又会退化为链表
- 扩容:当
HashMap
中的元素数量超过数组大小的一定比例(负载因子,默认为 0.75)时,HashMap
会进行扩容,即创建一个新的、更大的数组,并将现有元素重新哈希到新数组中。扩容是一个相对昂贵的操作,因为它涉及到重新计算所有元素的哈希值和重新分配元素。 - 哈希值优化:为了减少哈希冲突和提高性能,
HashMap
的键对象需要实现hashCode()
和equals()
方法。hashCode()
方法应返回键对象的哈希码,而equals()
方法则用于比较两个键对象是否相等。
这是HashMap底层的数组【桶】
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}