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 operationint 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;}
}