HashMap在高并发下如果没有处理线程安全会有怎样的安全隐患,具体表现是什么
Hashmap在并发环境下,可能出现的问题:
1、多线程put时可能会导致get无限循环,具体表现为CPU使用率100%;
原因:在向HashMap put元素时,会检查HashMap的容量是否足够,如果不足,则会新建一个比原来容量大两倍的Hash表,然后把数组从老的Hash表中迁移到新的Hash表中,迁移的过程就是一个rehash()的过程,多个线程同时操作就有可能会形成循环链表,所以在使用get()时,就会出现Infinite Loop的情况
// tranfer()片段 // 这是在resize()中调用的方法,resize()就是HashMap扩容的方法 for (int j = 0; j < src.length; j++) { Entry e = src[j]; if (e != null) { src[j] = null; do { Entry next = e.next; //假设线程1停留在这里就挂起了,线程2登场 int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } }
线程一:Thread1影响key1
线程二:Thread1影响key2
因为两条线程的影响,倒是出现循环的情况
出现问题的测试代码:
import java.util.HashMap; import java.util.concurrent.atomic.AtomicInteger; public class MyThread extends Thread { /** * 类的静态变量是各个实例共享的,因此并发的执行此线程一直在操作这两个变量 * 选择AtomicInteger避免可能的int++并发问题 */ private static AtomicInteger ai = new AtomicInteger(0); //初始化一个table长度为1的哈希表 private static HashMap<Integer, Integer> map = new HashMap<Integer, Integer>(1); //如果使用ConcurrentHashMap,不会出现类似的问题 // private static ConcurrentHashMap<Integer, Integer> map = new ConcurrentHashMap<Integer, Integer>(1); public void run() { while (ai.get() < 100000) { //不断自增 map.put(ai.get(), ai.get()); ai.incrementAndGet(); } System.out.println(Thread.currentThread().getName() + "线程即将结束"); } public static void main(String[] args) { MyThread t0 = new MyThread(); MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); MyThread t3 = new MyThread(); MyThread t4 = new MyThread(); MyThread t5 = new MyThread(); MyThread t6 = new MyThread(); MyThread t7 = new MyThread(); MyThread t8 = new MyThread(); MyThread t9 = new MyThread(); t0.start(); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); t6.start(); t7.start(); t8.start(); t9.start(); } }
2、多线程put时可能导致元素丢失
原因:当多个线程同时执行addEntry(hash,key ,value,i)时,如果产生哈希碰撞,导致两个线程得到同样的bucketIndex去存储,就可能会发生元素覆盖丢失的情况
void addEntry(int hash, K key, V value, int bucketIndex) { //多个线程操作数组的同一个位置 Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<K,V>(hash, key, value, e); if (size++ >= threshold) resize(2 * table.length); }
建议:
使用Hashtable 类,Hashtable 是线程安全的;
使用并发包下的java.util.concurrent.ConcurrentHashMap,ConcurrentHashMap实现了更高级的线程安全;
或者使用synchronizedMap() 同步方法包装 HashMap object,得到线程安全的Map,并在此Map上进行操作。