青州网站建设推广,西安市建设工程,天津原筑展览展示有限公司,网站开发能进无形资产吗HashMap在多线程环境下使用时存在死链问题。让我详细解释一下这个问题的原因以及如何规避#xff1a;
问题原因分析
在JDK 7及之前版本的HashMap中#xff0c;resize时采用的是头插法重新排列链表#xff0c;这会导致链表顺序反转#xff1a;
// JDK 7的resize代码片段
voi…HashMap在多线程环境下使用时存在死链问题。让我详细解释一下这个问题的原因以及如何规避问题原因分析在JDK 7及之前版本的HashMap中resize时采用的是头插法重新排列链表这会导致链表顺序反转// JDK 7的resize代码片段voidtransfer(Entry[]newTable){Entry[]srctable;intnewCapacitynewTable.length;for(intj0;jsrc.length;j){EntryK,Vesrc[j];if(e!null){src[j]null;do{EntryK,Vnexte.next;// 关键点线程A在这里读取nextintiindexFor(e.hash,newCapacity);e.nextnewTable[i];// 头插法新节点插入到链表头部newTable[i]e;enext;}while(e!null);}}}死链产生过程线程A和B同时进行resize线程A执行到EntryK,V next e.next;后被挂起线程B完成resize链表顺序被反转线程A恢复执行此时它的next指向的是反转前的下一个节点形成环形链表导致后续操作无限循环解决方案1. 使用线程安全的集合类ConcurrentHashMap推荐// Java 8 推荐ConcurrentHashMapString,ObjectconcurrentMapnewConcurrentHashMap();// 指定初始容量和并发级别ConcurrentHashMapString,ObjectmapnewConcurrentHashMap(16,0.75f,16);特点JDK 8采用CAS synchronized性能更好分段锁JDK 7或桶级锁JDK 8读写操作大部分情况下不需要全局锁Collections.synchronizedMapMapString,ObjectsynchronizedMapCollections.synchronizedMap(newHashMap());特点对整个map加锁性能较差2. 显式加锁控制// 使用ReentrantLockpublicclassSafeHashMapK,V{privatefinalMapK,VmapnewHashMap();privatefinalReentrantLocklocknewReentrantLock();publicVput(Kkey,Vvalue){lock.lock();try{returnmap.put(key,value);}finally{lock.unlock();}}publicVget(Kkey){lock.lock();try{returnmap.get(key);}finally{lock.unlock();}}}3. 使用其他并发数据结构CopyOnWrite适合读多写少CopyOnWriteArrayListStringlistnewCopyOnWriteArrayList();CopyOnWriteArraySetStringsetnewCopyOnWriteArraySet();ConcurrentLinkedQueue/DequeConcurrentLinkedQueueStringqueuenewConcurrentLinkedQueue();ConcurrentLinkedDequeStringdequenewConcurrentLinkedDeque();4. 使用读写锁ReadWriteLockpublicclassReadWriteMapK,V{privatefinalMapK,VmapnewHashMap();privatefinalReadWriteLocklocknewReentrantReadWriteLock();publicVput(Kkey,Vvalue){lock.writeLock().lock();try{returnmap.put(key,value);}finally{lock.writeLock().unlock();}}publicVget(Kkey){lock.readLock().lock();try{returnmap.get(key);}finally{lock.readLock().unlock();}}}版本差异说明JDK 8的改进JDK 8中的HashMap在resize时保持了链表元素的顺序尾插法但仍然不是线程安全的只是减少了死链的概率仍可能出现数据丢失、size计算错误等问题// JDK 8使用尾插法if(loTail!null){loTail.nextnull;newTab[j]loHead;// 保持原顺序}最佳实践建议1. 单线程环境使用HashMap性能最好2. 多线程环境读写都频繁ConcurrentHashMap写少读多CopyOnWrite系列需要强一致性Collections.synchronizedMap或显式加锁高并发计数LongAdder、ConcurrentHashMap的原子操作3. 特定场景// 统计计数ConcurrentHashMapString,LongAddercounternewConcurrentHashMap();counter.computeIfAbsent(key,k-newLongAdder()).increment();// 缓存实现ConcurrentHashMapString,FutureTcachenewConcurrentHashMap();4. 代码示例线程安全的缓存publicclassThreadSafeCacheK,V{privatefinalConcurrentHashMapK,VcachenewConcurrentHashMap();privatefinalConcurrentHashMapK,FutureVfuturesnewConcurrentHashMap();publicVget(Kkey,CallableVloader)throwsException{Vvaluecache.get(key);if(valuenull){FutureVfuturefutures.computeIfAbsent(key,k-CompletableFuture.supplyAsync(()-{try{returnloader.call();}catch(Exceptione){thrownewRuntimeException(e);}}));valuefuture.get();cache.put(key,value);futures.remove(key);}returnvalue;}}总结方案线程安全性能适用场景HashMap不安全最高单线程环境ConcurrentHashMap安全高高并发读写Collections.synchronizedMap安全低简单同步需求显式加锁安全中复杂同步逻辑CopyOnWrite安全读高写低读多写少核心建议在多线程环境下永远不要使用HashMap首选ConcurrentHashMap它在绝大多数场景下都能提供良好的性能和线程安全