zoukankan      html  css  js  c++  java
  • Java容器解析系列(11) HashMap 详解

    本篇我们来介绍一个最常用的Map结构——HashMap

    关于HashMap,关于其基本原理,网上对其进行讲解的博客非常多,且很多都写的比较好,所以....

    这里直接贴上地址:

    关于hash算法:

    Hash算法

    Hash时取模一定要模质数吗?

    关于HashMap:

    深入Java集合学习系列:HashMap的实现原理

    漫画:什么是HashMap?

    JDK 源码中 HashMap 的 hash 方法原理是什么?

    What is the use of Holder class in HashMap?(HashMap.Holder)

    JDK7与JDK8中HashMap的实现(jdk8对HashMap的红黑树改进)

    读完上面的内容,应该对HashMap有了很深的理解,这里补充1点助于深入的理解:

    高并发情况下,HashMap为什么会出现死循环

    漫画:高并发下的HashMap

    疫苗:JAVA HASHMAP的死循环

    上述博客均对多线程情况下的HashMap出现死循环问题的原理进行了解释,但是没有提供demo进行参考,我在网上也试图找相应的demo,但是没有能够100%复现的;这里我们直接参考疫苗:JAVA HASHMAP的死循环中的数据和过程,修改HashMap源码,提供循环demo,并阐述死锁发生的原因,及如何解决死循环:

    首先,修改HashMap源码如下:

    import java.util.Map;
    
    public class HashMap<K,V>{
    	
    	private transient Entry[] table;
    	private transient int size;
    	private int threshold;
    	private final float loadFactor;
    	transient int modCount;
    	
    	public HashMap(int initialCapacity,float loadFactor){
    		int capacity = 1;
    		while(capacity < initialCapacity)
    			capacity <<= 1;
    		this.loadFactor = loadFactor;
    		threshold = (int)(capacity * loadFactor);
    		table = new Entry[capacity];
    	}
    	
    	// 所有的hash值都是1
    	final int hash(Object k){
    		return 1;
    	}
    	
    	// 让所有的值的索引都是1
    	static int indexFor(int h,int length){
    		return 1;
    	}
    	
    	public V get(Object key){
    		Entry<K,V> entry = getEntry(key);
    		return null == entry ? null : entry.getValue();
    	}
    	
    	final Entry<K,V> getEntry(Object key){
    		int hash = (key == null) ? 0 : hash(key);
    		for(Entry<K,V> e = table[indexFor(hash,table.length)];
    		    e != null;
    		    e = e.next){
    			System.out.println("e.getKey():" + e.getKey());
    			Object k;
    			if(e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))){
    				return e;
    			}
    		}
    		return null;
    	}
    	
    	public V put(K key,V value,boolean delay){
    		int hash = hash(key);
    		int i = indexFor(hash,table.length);
    		for(Entry<K,V> e = table[i];e != null;e = e.next){
    			Object k;
    			if(e.hash == hash && ((k = e.key) == key || key.equals(k))){
    				V oldValue = e.value;
    				e.value = value;
    				return oldValue;
    			}
    		}
    		modCount++;
    		addEntry(hash,key,value,i,delay);
    		return null;
    	}
    	
    	void addEntry(int hash,K key,V value,int bucketIndex,boolean delay){
    		if((size >= threshold) && (null != table[bucketIndex])){
    			resize(2 * table.length,delay);
    			hash = (null != key) ? hash(key) : 0;
    			bucketIndex = indexFor(hash,table.length);
    		}
    		createEntry(hash,key,value,bucketIndex);
    	}
    	
    	void createEntry(int hash,K key,V value,int bucketIndex){
    		Entry<K,V> e = table[bucketIndex];
    		table[bucketIndex] = new Entry<>(hash,key,value,e);
    		size++;
    	}
    	
    	// 扩容,添加了delay参数,用于在测试过程中进行测试
    	void resize(int newCapacity,boolean delay){
    		Entry[] newTable = new Entry[newCapacity];
    		transfer(newTable,delay);
    		threshold = (int)(newCapacity * loadFactor);
    	}
    	
    	// 原有的HashMap的transfer函数,会导致死循环,且有对象被丢弃
    	// 添加延时选项,用于手动控制多个线程的相对运行过程
    	void transfer(Entry<K,V>[] newTable,boolean delay){
    		System.out.println("transfer in	" + Thread.currentThread().toString());
    		int newCapacity = newTable.length;
    		for(Entry e : table){
    			while(null != e){
    				Entry<K,V> next = e.next;
    
    				if(delay){
    					try{
    						Thread.sleep(20);
    					}catch(InterruptedException e1){
    						e1.printStackTrace();
    					}
    				}
    				int i = indexFor(e.hash,newCapacity);
    				e.next = newTable[i];
    				newTable[i] = e;
    				e = next;
    			}
    		}
    		System.out.println("transfer out	" + Thread.currentThread().toString());
    	}
    	
    	// 解决死循环
    	// 方式: 扩容后的数组,在对象rehash的过程中,如果某个位置出现冲突,采用尾插法将Entry插入链表
    	// 原理: 出现死循环的原因:在rehash过程中,对原数组某个位置的链表,采用从头开始的遍历方式,在新数组中,如果出现冲突,采用头插法将Entry插入链表
    	// 注意: 这里虽然解决了死循环的问题,但是因为并发修改对象内容,导致遍历过程中某些对象被丢弃的问题还是存在,
    	//       所以还是老老实实地用ConcurrentHashMap或者Collections.synchronizedMap()吧
    	//void transfer(Entry<K,V>[] newTable,boolean delay){
    	//	System.out.println("transfer in	" + Thread.currentThread().toString());
    	//	int newCapacity = newTable.length;
    	//	for(Entry e : table){
    	//		while(null != e){
    	//			Entry<K,V> next = e.next;
    	//			if(delay){
    	//				try{
    	//					Thread.sleep(20);
    	//				}catch(InterruptedException e1){
    	//					e1.printStackTrace();
    	//				}
    	//			}
    	//			int i = indexFor(e.hash,newCapacity);
    	//			Entry tmp = newTable[i];
    	//			if(tmp == null){
    	//				newTable[i] = e;
    	//				newTable[i].next = null;
    	//			}else{
    	//				// 尾插法
    	//				while(tmp.next != null){
    	//					System.out.println(tmp.next.getKey());
    	//					System.out.println("----------------------");
    	//					tmp = tmp.next;
    	//				}
    	//				tmp.next = e;
    	//				tmp.next = null;
    	//			}
    	//			e = next;
    	//		}
    	//	}
    	//	System.out.println("transfer out	" + Thread.currentThread().toString());
    	//}
    	
    	static class Entry<K,V> implements Map.Entry<K,V>{
    		final K key;
    		V value;
    		Entry<K,V> next;
    		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;
    		}
    		// hashCode永远为1
    		public final int hashCode(){
    			return 1;
    		}
    		public final String toString(){
    			return getKey() + "=" + getValue();
    		}
    	}
    }
    
    public class HashMapInfinitLoop{
    	
    	public static void main(String[] args) throws InterruptedException{
    		HashMap<Integer,Integer> map = new HashMap<>(4,0.8f);
    		map.put(5,55,false);
    		map.put(7,77,false);
    		map.put(3,33,false);
    		
    		new Thread("Thread1"){
    			public void run(){
    				map.put(17,77,true);
    				System.out.println("put 17 finished");
    			}
    		}.start();
    
    		new Thread("Thread2"){
    			public void run(){
    				map.put(23,33,false);
    				System.out.println("put 23 finished");
    			}
    		}.start();
    
    		Thread.sleep(2000);
    		// 此处get()死循环
    		System.out.println(map.get(5));
    		
    	}
    	
    }
    

    运行上述demo,控制台输出如下:

    transfer in	Thread[Thread1,5,main]
    transfer in	Thread[Thread2,5,main]
    transfer out	Thread[Thread2,5,main]
    put 23 finished
    transfer out	Thread[Thread1,5,main]
    put 17 finished
    e.getKey():17
    e.getKey():23
    e.getKey():3
    e.getKey():7
    e.getKey():3
    e.getKey():7
    .......
    

    注释掉原有的transfer(),使用解决死循环的transfer(),运行结果如下:

    transfer in	Thread[Thread1,5,main]
    transfer in	Thread[Thread2,5,main]
    transfer out	Thread[Thread2,5,main]
    put 23 finished
    transfer out	Thread[Thread1,5,main]
    put 17 finished
    e.getKey():17
    e.getKey():23
    e.getKey():3
    null
    

    发现死循环问题是没有了,但是还是存在数据被丢弃的情况.

    so,it sucks

  • 相关阅读:
    tomcat7项目启动报错java.lang.NoClassDefFoundError: org/apache/juli/logging/LogFactory
    Android APP 分享图片文字到微信刚開始正常,后面就不弹出分享框了
    Akka 编程: 什么是Actor
    【3C认证】安防产品3C认证
    设计模式之装饰模式
    关于那些常见的坑爹的小bug(会持续更新)
    【Mac双系统设置系统默认启动系统】解决方式
    JEECG常见问题大全征集
    关于最新的Vuforia-unity3d-samples2-8-13
    python-print
  • 原文地址:https://www.cnblogs.com/jamesvoid/p/9809452.html
Copyright © 2011-2022 走看看