我们都知道HashMap和Hashtable的区别是:
HashMap线程不安全,允许NULL值和Null键。
Hashtable线程安全,不允许Null值和Null键。
下面我们用多线程去分别操作HashMap和Hashtable看看会出现什么状况。
首先是用多线程去操作HashMap:
package com.xql.thread.MyThread;
import java.util.HashMap;
import java.util.Map;
public class HashMapManyThread {
static Map<String,String > map =new HashMap<String, String>(16);//初始化容量
public static class TestHashMapThread implements Runnable{
int start=0;
public TestHashMapThread(int start){
this.start=start;
}
@Override
public void run() {
for (int i = 0; i <100000 ; i+=2) {
System.out.println("--puting----");
map.put(Integer.toString(i),String.valueOf(Math.random()*100));
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads =new Thread[100];
for (int i = 0; i <threads.length ; i++) {
threads[i]=new Thread(new TestHashMapThread(i));
}
for (int i = 0; i <100 ; i++) {
threads[i].start();
}
System.out.println(map.size());
}
}
直接造成了cpu100%的占用,我是刚不住了只好停下来了,怕爆炸!!然后我们利用jstack去查看线程状态
我们可以看到线程已经进入了运行中而且还是循环中,后面进入了死锁状态。这就体现了hashmap的线程不安全。
接着我们来用多线程操作hashtable:
package com.xql.thread.demo2;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
public class MapTest {
// public static ConcurrentHashMap cmap = new ConcurrentHashMap();
public static Hashtable cmap = new Hashtable();
public static void main(String[] args) {
// TODO Auto-generated method stub
MapThreadAdd mt2 = new MapThreadAdd();
Thread addThread = new Thread(mt2,"");
addThread.start();
try{
Thread.sleep(1);
}catch(Exception e){
}
while(true){
if(cmap.size()>=5){
Iterator<String> it = cmap.keySet().iterator();
while(it.hasNext()){
System.out.println("************************");
String key = it.next();
// it.remove();//通过Iterator修改Hashtable
String value = (String)cmap.get(key);
System.out.println("循环key:"+key);
}
}
}
}
}
package com.xql.thread.demo2;
public class MapThreadAdd implements Runnable{
public void run(){
for(int i=0;i<1000;i++){
MapTest.cmap.put("key"+i, i+"");
try{
Thread.sleep(10);
}catch(Exception e){
}
}
}
}
这是在hashtable一边插入一边遍历时就会出错, 原因:Iterator做遍历的时候,HashMap被修改(bb.remove(ele), size-1),Iterator(Object ele=it.next())会检查HashMap的size,size发生变化,抛出错误ConcurrentModificationException。
解决办法:
通过Iterator修改Hashtable
while(it.hasNext()) {
Object ele = it.next();
it.remove();
}根据实际程序,您自己手动给Iterator遍历的那段程序加锁,给修改HashMap的那段程序加锁。
使用“ConcurrentHashMap”替换HashMap,ConcurrentHashMap会自己检查修改操作,对其加锁,也可针对插入操作。
import java.util.concurrent.*;
上面我们说了hashmap和hashtable的线程问题,下面我们说说hashmap和hashtable的工作原理:
hashmap的底层是数组+链表在jdk1.8后底层是数组+链表+二叉树。它的工作原理是:HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象。”这里关键点在于指出,HashMap是在bucket中储存键对象和值对象,作为Map.Entry。HashMap的put方法对key的hashcode做hash,然后通过putVal来进行存储值
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
1、首先判断键值对数组是否null长度是否为0,如果是数组为null长度为0,就对数组进行扩容。
2、通过key的hash计算数组的索引,这就是key的位置
3、如果该节点没有数据就返回为null,如果数据是一个二叉树,那就执行增加操作,如果是链表分两种情况一是该链表没有这个节点,另一个是该链表上有这个节点,注意这里判断的依据是key.hash是否一样:
如果该链表没有这个节点,那么采用尾插法新增节点保存新数据,返回null;如果该链表已经有这个节点了,那么找到该节点并更新新数据,返回老数据。final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n – 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD – 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
Hashmap的get方法:通过key的hashcode找到对应节点,通过getNode实现
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
1、判断数组不为空,长度大于0且数据已经初始化,根据hash寻找数组中的项不为null
2、bucket中第一项相等,且不止一个节点
3、为二叉树节点时在二叉树中寻找
4、为链表时在链表中寻找
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
下面是hashtable的工作原理:
hashtable之所以是线程安全的,原因为在put和get方法上使用synchronized关键字进行修饰。
通过key的hashcode值的寻址,找到在table数组中的位置index.
遍历table[index]链表,找到key值相同的节点的value返回,注意同样在该过程中使用到了key的equal方法,所以key被应用与hashtable时不仅要实现hashcode方法还有实现equal方法。
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
1、有synchronized 关键字保证其线程安全
2、定义了value不能为null,否则会报空指针异常
3、通过key的hashcode找到对应值在数组中的位置
4、遍历table[index]所连接的链表,查找是否已经存在key与需要插入的key值相同的节点,如果存在则直接更新value,并返回旧的value。
5、如果table[index]所连接的链表上不存在相同的key,则通过addEntry()方法将新节点加载链表的开头。
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
hashmap继承了AbstractMap实现了map。Cloneable,Serializable 接口
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
hashtable继承了Dictionary实现了map,Cloneable,Serializable 接口
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable