常用集合在多线程下的问题,主要列举了collection接口,map接口及其主要实现类在多线程下的问题
hashset,无序,不可重复,判断插入的两个对象重复与否要看有没有重写实体的equals和hashcode方法
arraylist:有序,可重复,有讲解各种遍历删除在单线程下的正确与否,以及在多线程的问题下以及解决方案,
单线程下下面的写法会报异常:
Iterator<String>it = list.iterator(); while(it.hasNext()) { String value = it.next(); //cursor的值为1,lastRet的值为0。注意此时,modCount为0,expectedModCount也为0。 if(value.equals("1")) { list.remove(value); //此时modCount为1,而expectedModCount为0 } }原因解析:修改次数和预期期望修改次数不一致,报错
这关系到arraylist的源码,arraylist的底层实现是数组
remove源码:
public E remove(int index) { rangeCheck(index);//有没有越界 modCount++;//修改次数 E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved);//数组自删除,效率较高 elementData[--size] = null; // clear to let GC do its work return oldValue; }itr源码:
private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet);//调用上面写的删除方法 cursor = lastRet; lastRet = -1; expectedModCount = modCount;//修改期望修改次数=修改次数 } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException();//抛出的是这里的异常 } } @Override @SuppressWarnings("unchecked") public void forEachRemaining(Consumer<? super E> consumer) { Objects.requireNonNull(consumer); final int size = ArrayList.this.size; int i = cursor; if (i >= size) { return; } final Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) { throw new ConcurrentModificationException(); } while (i != size && modCount == expectedModCount) { consumer.accept((E) elementData[i++]); } // update once at end of iteration to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } final void checkForComodification() {//检查是否可修改 if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }单线程下解决方案:修改为:
Iterator<String>it = list.iterator(); while(it.hasNext()) { String value = it.next(); if(value.equals("1")) { it.remove(); } }但是,在多线程下他是会报异常的,解决方案:
使用线程安全的类:
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>()
或者:List<String> data = Collections.synchronizedList(new ArrayList<String>())
注意:这里的线程安全是指put add remove等是原子性操作,并不表示组合操作仍是线程安全的。hashmap在多线程下的问题:
1.多线程扩容并且get的时候会死循环
2.put非空的元素get的时候为空
3.多线程put元素某些数据会丢失
hashmap多线程下死循环问题及解决方案:
这也关系到hashmap的底层实现结构是数据加链表,table[i]位置存放的是entry对象组成的链表
// 默认初始容量为16,必须为2的n次幂
static
final
int
DEFAULT_INITIAL_CAPACITY =
16
;
// 最大容量为2的30次方
static
final
int
MAXIMUM_CAPACITY =
1
<<
30
;
// 默认加载因子为0.75f
static
final
float
DEFAULT_LOAD_FACTOR =
0
.75f;
// Entry数组,长度必须为2的n次幂
transient
Entry[] table;
// 已存储元素的数量
transient
int
size ;
// 下次扩容的临界值,size>=threshold就会扩容,threshold等于capacity*load factor
int
threshold;
// 加载因子
final
float
loadFactor ;
enrty类:
static class Entry<K,V> implements Map.Entry<K,V> { final K key ; V value; Entry<K,V> next; // 指向下一个节点 final int hash; Entry( int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } public final K getKey() { return key ; } public final V getValue() { return value ; } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; Object k1 = getKey(); Object k2 = e.getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { Object v1 = getValue(); Object v2 = e.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) return true; } return false; } public final int hashCode() { return (key ==null ? 0 : key.hashCode()) ^ ( value==null ? 0 : value.hashCode()); } public final String toString() { return getKey() + "=" + getValue(); } // 当向HashMap中添加元素的时候调用这个方法,这里没有实现是供子类回调用 void recordAccess(HashMap<K,V> m) { } // 当从HashMap中删除元素的时候调动这个方法 ,这里没有实现是供子类回调用 void recordRemoval(HashMap<K,V> m) { } }
hashmap的构造方法:
/** * 构造一个指定初始容量和加载因子的HashMap */ public HashMap( int initialCapacity, float loadFactor) { // 初始容量和加载因子合法校验 if (initialCapacity < 0) throw new IllegalArgumentException( "Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException( "Illegal load factor: " + loadFactor); // Find a power of 2 >= initialCapacity // 确保容量为2的n次幂,是capacity为大于initialCapacity的最小的2的n次幂 int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; // 赋值加载因子 this.loadFactor = loadFactor; // 赋值扩容临界值 threshold = (int)(capacity * loadFactor); // 初始化hash表 table = new Entry[capacity]; init(); } /** * 构造一个指定初始容量的HashMap */ public HashMap( int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } /** * 构造一个使用默认初始容量(16)和默认加载因子(0.75)的HashMap */ public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); table = new Entry[DEFAULT_INITIAL_CAPACITY]; init(); } /** * 构造一个指定map的HashMap,所创建HashMap使用默认加载因子(0.75)和足以容纳指定map的初始容量。 */ public HashMap(Map<? extends K, ? extends V> m) { // 确保最小初始容量为16,并保证可以容纳指定map this(Math.max(( int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY ), DEFAULT_LOAD_FACTOR); putAllForCreate(m); }
主要看put的实现:
public V put(K key, V value) { // 如果key为null,调用putForNullKey方法进行存储 if (key == null) return putForNullKey(value); // 使用key的hashCode计算key对应的hash值 int hash = hash(key.hashCode()); // 通过key的hash值查找在数组中的index位置 int i = indexFor(hash, table.length ); // 取出数组index位置的链表,遍历链表找查看是有已经存在相同的key for (Entry<K,V> e = table [i]; e != null; e = e. next) { Object k; // 通过对比hash值、key判断是否已经存在相同的key if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { // 如果存在,取出当前key对应的value,供返回 V oldValue = e. value; // 用新value替换之旧的value e. value = value; e.recordAccess( this); // 返回旧value,退出方法 return oldValue; } } // 如果不存在相同的key // 修改版本+1 modCount++; // 在数组i位置处添加一个新的链表节点 addEntry(hash, key, value, i); // 没有相同key的情况,返回null return null; } private V putForNullKey(V value) { // 取出数组第1个位置(下标等于0)的节点,如果存在则覆盖不存在则新增,和上面的put一样不多讲, for (Entry<K,V> e = table [0]; e != null; e = e. next) { if (e.key == null) { V oldValue = e. value; e. value = value; e.recordAccess( this); return oldValue; } } modCount++; // 如果key等于null,则hash值等于0 addEntry(0, null, value, 0); return null; }
计算这个值在table中的位置和这个值的hashcode
static int hash(int h) { // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } /** * Returns index for hash code h. */ static int indexFor(int h, int length) { return h & (length-1); }
/** * 增加一个k-v,hash组成的节点在数组内,同时可能会进行数组扩容。 */ void addEntry( int hash, K key, V value, int bucketIndex) { // 下面两行行代码的逻辑是,创建一个新节点放到单向链表的头部,旧节点向后移 // 取出索引bucketIndex位置处的链表节点,如果节点不存在那就是null,也就是说当数组该位置处还不曾存放过节点的时候,这个地方就是null, Entry<K,V> e = table[bucketIndex]; // 创建一个节点,并放置在数组的bucketIndex索引位置处,并让新的节点的next指向原来的节点 table[bucketIndex] = new Entry<K,V>(hash, key, value, e); // 如果当前HashMap中的元素已经到达了临界值,则将容量扩大2倍,并将size计数+1 if (size ++ >= threshold) resize(2 * table.length ); }
扩容的实现:
void resize( int newCapacity) { // 当前数组 Entry[] oldTable = table; // 当前数组容量 int oldCapacity = oldTable.length ; // 如果当前数组已经是默认最大容量MAXIMUM_CAPACITY ,则将临界值改为Integer.MAX_VALUE 返回 if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } // 使用新的容量创建一个新的链表数组 Entry[] newTable = new Entry[newCapacity]; // 将当前数组中的元素都移动到新数组中 transfer(newTable); // 将当前数组指向新创建的数组 table = newTable; // 重新计算临界值 threshold = (int)(newCapacity * loadFactor); } /** * Transfers all entries from current table to newTable. */ void transfer(Entry[] newTable) { // 当前数组 Entry[] src = table; // 新数组长度 int newCapacity = newTable.length ; // 遍历当前数组的元素,重新计算每个元素所在数组位置 for (int j = 0; j < src. length; j++) { // 取出数组中的链表第一个节点 Entry<K,V> e = src[j]; if (e != null) { // 将旧链表位置置空,因为这里出现的put非空数据get为空 src[j] = null; // 循环链表,挨个将每个节点插入到新的数组位置中 do { // 取出链表中的当前节点的下一个节点 Entry<K,V> next = e. next; // 重新计算该链表在数组中的索引位置 int i = indexFor(e. hash, newCapacity); // 将下一个节点指向newTable[i] e. next = newTable[i]; // 将当前节点放置在newTable[i]位置 newTable[i] = e; // 下一次循环 e = next; } while (e != null); } } }
删除:
/** * 根据key删除元素 */ public V remove(Object key) { Entry<K,V> e = removeEntryForKey(key); return (e == null ? null : e. value); } /** * 根据key删除链表节点 */ final Entry<K,V> removeEntryForKey(Object key) { // 计算key的hash值 int hash = (key == null) ? 0 : hash(key.hashCode()); // 根据hash值计算key在数组的索引位置 int i = indexFor(hash, table.length ); // 找到该索引出的第一个节点 Entry<K,V> prev = table[i]; Entry<K,V> e = prev; // 遍历链表(从链表第一个节点开始next),找出相同的key, while (e != null) { Entry<K,V> next = e. next; Object k; // 如果hash值和key都相等,则认为相等 if (e.hash == hash && ((k = e. key) == key || (key != null && key.equals(k)))) { // 修改版本+1 modCount++; // 计数器减1 size--; // 如果第一个就是要删除的节点(第一个节点没有上一个节点,所以要分开判断) if (prev == e) // 则将下一个节点放到table[i]位置(要删除的节点被覆盖) table[i] = next; else // 否则将上一个节点的next指向当要删除节点下一个(要删除节点被忽略,没有指向了) prev. next = next; e.recordRemoval( this); // 返回删除的节点内容 return e; } // 保存当前节点为下次循环的上一个节点 prev = e; // 下次循环 e = next; } return e; }
get会遍历整个table[i]指向的链表,效率并不高
public V get(Object key) { // 如果key等于null,则调通getForNullKey方法 if (key == null) return getForNullKey(); // 计算key对应的hash值 int hash = hash(key.hashCode()); // 通过hash值找到key对应数组的索引位置,遍历该数组位置的链表 for (Entry<K,V> e = table [indexFor (hash, table .length)]; e != null; e = e. next) { Object k; // 如果hash值和key都相等,则认为相等 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) // 返回value return e.value ; } return null; } private V getForNullKey() { // 遍历数组第一个位置处的链表 for (Entry<K,V> e = table [0]; e != null; e = e. next) { if (e.key == null) return e.value ; } return null; }是否包含的判断:
/** * Returns <tt>true</tt> if this map contains a mapping for the * specified key. * * @param key The key whose presence in this map is to be tested * @return <tt> true</tt> if this map contains a mapping for the specified * key. */ public boolean containsKey(Object key) { return getEntry(key) != null; } /** * Returns the entry associated with the specified key in the * HashMap. Returns null if the HashMap contains no mapping * for the key. */ final Entry<K,V> getEntry(Object key) { int hash = (key == null) ? 0 : hash(key.hashCode()); for (Entry<K,V> e = table [indexFor (hash, table .length)]; e != null; e = e. next) { Object k; if (e.hash == hash && ((k = e. key) == key || (key != null && key.equals(k)))) return e; } return null; }
解决方案:
使用线程安全的ConcurrentHashMap
使用线程安全的hashtable,效率不高