×

hashmap线程不安全的原因及表现

hashmap线程不安全的原因及表现(hashmap是线程安全的吗,为什么不是线程安全的)

admin admin 发表于2023-10-22 03:54:42 浏览40 评论0

抢沙发发表评论

本文目录

hashmap是线程安全的吗,为什么不是线程安全的

我之前在讲课中,给学生强调这个问题,这个是必需要会的,面试中90%的机会会问到你其实就是HashMap Hashtable区别1、前者非线程安全,后者线程安全2、前者效率高,后者低(如果说到非线程安全,你可以闭着眼睛,下一点就说他的效率高,这是相对的)3、其实是不是线程安全,你可以查看源代码,他里面的一些对元素变动的方法中,有没有这个关键字:synchronized

hashmap 为什么是线程不安全

当多个线程同时检测到总数量超过门限值的时候就会同时调用resize操纵,各自生成新的数组并rehash后赋给该map底层的数组table,结果最终只有最后一个线程生成的新数组被赋给table变量,其他线程的均会丧失。而且当某些线程已经完成赋值而其他线程刚开始的时候,就会用已经被赋值的table作为原始数组,这样也会有问题。

写个例子说明HashMap线程不安全

在平时开发中,我们经常采用HashMap来作为本地缓存的一种实现方式,将一些如系统变量等数据量比较少的参数保存在HashMap中,并将其作为单例类的一个属性。在系统运行中,使用到这些缓存数据,都可以直接从该单例中获取该属性集合。但是,最近发现,HashMap并不是线程安全的,如果你的单例类没有做代码同步或对象锁的控制,就可能出现异常。首先看下在多线程的访问下,非现场安全的HashMap的表现如何,在网上看了一些资料,自己也做了一下测试:1public class MainClass {23 public static final HashMap《String, String》 firstHashMap=new HashMap《String, String》();45 public static void main(String args) throws InterruptedException {67 //线程一8 Thread t1=new Thread(){9 public void run() {10 for(int i=0;i《25;i++){11 firstHashMap.put(String.valueOf(i), String.valueOf(i));12 }13 }14 };15 16 //线程二17 Thread t2=new Thread(){18 public void run() {19 for(int j=25;j《50;j++){20 firstHashMap.put(String.valueOf(j), String.valueOf(j));21 }22 }23 };24 25 t1.start();26 t2.start();27 28 //主线程休眠1秒钟,以便t1和t2两个线程将firstHashMap填装完毕。29 Thread.currentThread().sleep(1000);30 31 for(int l=0;l《50;l++){32 //如果key和value不同,说明在两个线程put的过程中出现异常。33 if(!String.valueOf(l).equals(firstHashMap.get(String.valueOf(l)))){34 System.err.println(String.valueOf(l)+“:“+firstHashMap.get(String.valueOf(l)));35 }36 }37 38 }3940}上面的代码在多次执行后,发现表现很不稳定,有时没有异常文案打出,有时则有个异常出现:为什么会出现这种情况,主要看下HashMap的实现:1public V put(K key, V value) {2 if (key == null)3 return putForNullKey(value);4 int hash = hash(key.hashCode());5 int i = indexFor(hash, table.length);6 for (Entry《K,V》 e = table;10 transfer(newTable);11 table = newTable;12 threshold = (int)(newCapacity * loadFactor);13 }一般我们声明HashMap时,使用的都是默认的构造方法:HashMap《K,V》,看了代码你会发现,它还有其它的构造方法:HashMap(int initialCapacity, float loadFactor),其中参数initialCapacity为初始容量,loadFactor为加载因子,而之前我们看到的threshold = (int)(capacity * loadFactor); 如果在默认情况下,一个HashMap的容量为16,加载因子为0.75,那么阀值就是12,所以在往HashMap中put的值到达12时,它将自动扩容两倍,如果两个线程同时遇到HashMap的大小达到12的倍数时,就很有可能会出现在将oldTable转移到newTable的过程中遇到问题,从而导致最终的HashMap的值存储异常。当多个线程同时检测到总数量超过门限值的时候就会同时调用resize操作,各自生成新的数组并rehash后赋给该map底层的数组table,结果最终只有最后一个线程生成的新数组被赋给table变量,其他线程的均会丢失。而且当某些线程已经完成赋值而其他线程刚开始的时候,就会用已经被赋值的table作为原始数组,这样也会有问题。JDK1.0引入了第一个关联的集合类HashTable,它是线程安全的。HashTable的所有方法都是同步的。JDK2.0引入了HashMap,它提供了一个不同步的基类和一个同步的包装器synchronizedMap。synchronizedMap被称为有条件的线程安全类。JDK5.0util.concurrent包中引入对Map线程安全的实现ConcurrentHashMap,比起synchronizedMap,它提供了更高的灵活性。同时进行的读和写操作都可以并发地执行。所以在开始的测试中,如果我们采用ConcurrentHashMap,它的表现就很稳定,所以以后如果使用Map实现本地缓存,为了提高并发时的稳定性,还是建议使用ConcurrentHashMap。

hashmap为什么线程不安全

HashMap底层是一个Entry数组,当发生hash冲突的时候,hashmap是采用链表的方式来解决的,在对应的数组位置存放链表的头结点。对链表而言,新加入的节点会从头结点加入。javadoc中关于hashmap的一段描述如下:此实现不是同步的。如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须 保持外部同步。(结构上的修改是指添加或删除一个或多个映射关系的任何操作;仅改变与实例已经包含的键关联的值不是结构上的修改。)这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射进行意外的非同步访问

为什么hashmap线程不安全

一直以来只是知道HashMap是线程不安全的,但是到底HashMap为什么线程不安全,多线程并发的时候在什么情况下可能出现问题?HashMap底层是一个Entry数组,当发生hash冲突的时候,hashmap是采用链表的方式来解决的,在对应的数组位置存放链表的头结点。对链表而言,新加入的节点会从头结点加入。javadoc中关于hashmap的一段描述如下:此实现不是同步的。如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须 保持外部同步。(结构上的修改是指添加或删除一个或多个映射关系的任何操作;仅改变与实例已经包含的键关联的值不是结构上的修改。)这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射进行意外的非同步访问,如下所示: Map m = Collections.synchronizedMap(new HashMap(...));1、void addEntry(int hash, K key, V value, int bucketIndex) { Entry《K,V》 e = table; transfer(newTable); table = newTable; threshold = (int)(newCapacity * loadFactor); }这个操作会新生成一个新的容量的数组,然后对原数组的所有键值对重新进行计算和写入新的数组,之后指向新生成的数组。当多个线程同时检测到总数量超过门限值的时候就会同时调用resize操作,各自生成新的数组并rehash后赋给该map底层的数组table,结果最终只有最后一个线程生成的新数组被赋给table变量,其他线程的均会丢失。而且当某些线程已经完成赋值而其他线程刚开始的时候,就会用已经被赋值的table作为原始数组,这样也会有问题。

多线程操作HashMap是否线程安全,为什么

lan_153说得不错,HashMap用基于版本检查的方法来实现了一个乐观锁,但是由于其快速失败的特性,一旦版本产生冲突就抛出异常了,不会再有别的机会来对修改的内容做check in。所以最直接来说,多线程修改有可能导致HashMap抛出异常,修改会失败,所以不是线程安全的。

hashmap为什么不是线程安全的

首页所有文章资讯Web架构基础技术书籍教程Java小组工具资源谈谈HashMap线程不安全的体现2016/10/18|分类:基础技术|6条评论|标签:HASHMAP,并发分享到:26原文出处:HoseeHashMap的原理以及如何实现,之前在JDK7与JDK8中HashMap的实现中已经说明了。那么,为什么说HashMap是线程不安全的呢?它在多线程环境下,会发生什么情况呢?1.resize死循环我们都知道HashMap初始容量大小为16,一般来说,当有数据要插入时,都会检查容量有没有超过设定的thredhold,如果超过,需要增大Hash表的尺寸,但是这样一来,整个Hash表里的元素都需要被重算一遍。这叫rehash,这个成本相当的大。voidresize(intnewCapacity){EntryoldTable=table;intoldCapacity=oldTable.length;if(oldCapacity==MAXIMUM_CAPACITY){threshold=Integer.MAX_VALUE;return;}EntrynewTable=newEntry=e;e=next;}}}大概看下transfer:对索引数组中的元素遍历对链表上的每一个节点遍历:用next取得要转移那个元素的下一个,将e转移到新Hash表的头部,使用头插法插入节点。循环2,直到链表节点全部转移循环1,直到所有索引数组全部转移经过这几步,我们会发现转移的时候是逆序的。假如转移前链表顺序是1-》2-》3,那么转移后就会变成3-》2-》1。这时候就有点头绪了,死锁问题不就是因为1-》2的同时2-》1造成的吗?所以,HashMap的死锁问题就出在这个transfer()函数上。

为什么HashMap是线程不安全的

比如一个 ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:1. 在 Items 的位置存放此元素;2. 增大 Size 的值。 在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1; 而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。 那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。