精华内容
下载资源
问答
  • c++简单的线程安全HashMap

    千次阅读 2019-10-14 11:40:05
    说明:主要是记录自己的练习,不保证正确,请勿参考,且没有达到容量上限时自动扩容和重新hash #ifndef THREADSAFEHASHMAP_H #define THREADSAFEHASHMAP_H #include<functional> #include<...

    说明:主要是记录自己的练习,不保证正确,请勿参考,且没有达到容量上限时自动扩容和重新hash

     

    #ifndef THREADSAFEHASHMAP_H
    #define THREADSAFEHASHMAP_H
    #include<functional>
    #include<mutex>
    #include<utility>
    #include<list>
    #include<algorithm>
    #include<iostream>
    #include<vector>
    #include<memory>
    #include<map>
    
    namespace com{
        namespace example{
            namespace testone{
                namespace thread{
                    namespace data_structures{
    
    
    
    
    template<typename K, typename V, typename Hash = std::hash<K>>
    class ThreadSafeHashMap
    {
        private:
    
            class BucketType{
                public:
                    typedef std::pair<K, V> BucketValue;
                    typedef std::list<BucketValue> BucketData;
                    typedef typename std::list<BucketValue>::iterator BucketIterator;
                    BucketData mData;
                    std::mutex mtx;
    
                public:
                    bool checkIsFind(const BucketValue& bv, const K& key){
                        if(bv.first == key){
                            return true;
                        }
                        return false;
                    }
    
                    BucketIterator findByKey(const K& key){
                        //std::bind中的占位符_1,调用的时候由调用方传入,调用方传入的第一个参数替换_1
                        //本里中调用方传入的是BucketValue
                        auto it = std::find_if(this->mData.begin(), this->mData.end(),
                                                std::bind(&BucketType::checkIsFind, this, std::placeholders::_1, key));
                        return it;
                    }
    
                    V get(const K& key, V defaultValue){
                        std::lock_guard<std::mutex> lk(this->mtx);
                        BucketIterator it = this->findByKey(key);
                        if(it != this->mData.end()){
                            return it->second;
                        }
                        return defaultValue;
                    }
    
                    void removeByKey(const K& key){
                        std::lock_guard<std::mutex> lk(this->mtx);
                        BucketIterator it = this->findByKey(key);
                        if(it != this->mData.end()){
                            this->mData.erase(it);
                        }
                    }
    
                    void addOrUpdata(K key, V value){
                        std::lock_guard<std::mutex> lk(this->mtx);
                        //std::cout << "addOrUpdata(K key, V value)开始" << std::endl;
                        BucketIterator it = this->findByKey(key);
                        if(it != this->mData.end()){ //找到了就更新
                            //std::cout << "addOrUpdata(K key, V value)....找到了更新" << std::endl;
                            *it = std::move(std::pair<K, V>(std::move(key), std::move(value)));
                        }else{ //没找到就插入
                            //std::pair<K, V> p(std::move(key), std::move(value));
                            //std::cout << "addOrUpdata(K key, V value)....未找到插入" << std::endl;
                            this->mData.push_back(std::pair<K, V>(std::move(key), std::move(value)));
                        }
    
                    }
    
    
            };
    
        public:
            ThreadSafeHashMap(int num = 19, Hash hasher = Hash()){
                //this->mBuckets = std::vector<std::shared_ptr<BucketType>>(num);
                this->mHasher = std::move(hasher);
                for(int i = 0; i < num; i++){
                    this->mBuckets.push_back(std::shared_ptr<BucketType>(new BucketType));
                }
                //std::cout << "ThreadSafeHashMap()......mBuckets.size() = " << this->mBuckets.size() << std::endl;
            }
            virtual ~ThreadSafeHashMap(){
    
            }
        public:
            void addOrUpdateMy(K key, V value){
                //std::shared_ptr<BucketType> sp = this->positeByHash(key);
                this->positeByHash(key)->addOrUpdata(key, value);
            }
    
            V getMy(const K& key, V value){
                return this->positeByHash(key)->get(key, value);
            }
    
            void removeMy(const K& key){
                this->positeByHash(key)->removeByKey(key);
            }
    
            /**
            * 这个需要要把每一个BucketType都锁住,防止获取的时候有BucketType在修改
            */
            std::map<K, V> getAll(){
                //将每一个BucketType都锁住
                std::vector<std::unique_lock<std::mutex>> mtxs;
                for(int i = 0; i < this->mBuckets.size(); i++){
                    mtxs.push_back(std::move(std::unique_lock<std::mutex>(this->mBuckets.at(i)->mtx)));
                }
                //循环遍历取出内容
                std::map<K, V> mapTmp;
                for(int i = 0; i < this->mBuckets.size(); i++){
                    std::shared_ptr<BucketType> sp = this->mBuckets.at(i);
                    std::list<std::pair<K, V>>& bd = sp->mData;
                    for(auto it = bd.begin(); it != bd.end(); it++){
                        mapTmp.insert(*it);
                    }
                }
                return mapTmp;
    
            }
    
        protected:
    
        private:
            /**
            * 根据key计算hash值,根据hash值计算在vecotr中对应位置的BucketType
            */
            std::shared_ptr<BucketType> positeByHash(const K& key){
                std::size_t h = this->mHasher(key) % this->mBuckets.size();
                //std::cout << "positeByHash(const K& key)....h = " << h << std::endl;
                //std::cout << "this->mBuckets.size() = " << this->mBuckets.size() << std::endl;
                return this->mBuckets.at(h);
            }
    
        private:
            std::vector<std::shared_ptr<BucketType>> mBuckets;
            Hash mHasher;
    
    
    };
    
    
    
    
    
    
    
                    }
                }
            }
        }
    }
    
    #endif // THREADSAFEHASHMAP_H
    

     

    展开全文
  • 什么叫做线程安全 HashMap Hashtable Collections.synchronizedMap()


    什么叫做线程安全?
    在一个线程中,某操作执行之后得到的是该操作想要的结果,而不是其他结果(被其他线程修改了)

    一、HashMap解析

    HashMap是线程不安全的,多线程情况下不推荐使用HashMap。它的key,value运行为null

    二、Hashtable解析

    Hashtable在jdk1.1就有了,那么它是怎样实现线程安全的呢?主要看put、remove、get方法猜它肯定进行的同步控制的。于是看源码:

    //get它搞成了同步方法,保证了get的安全性
     public synchronized V get(Object key) {
           ……
        }
    
    //synchronized,同样
    public synchronized V put(K key, V value) {
           ……
        }
    //也是搞成了同步方法
    public synchronized V remove(Object key) {
         ……
        }
    

    所以为什么Hashtable是线程安全的,因为它的remove,put,get做成了同步方法,保证了Hashtable的线程安全性。

    每个操作数据的方法都进行同步控制之后,由此带来的问题任何一个时刻只能有一个线程可以操纵Hashtable,所以其效率比较低


    三、Collections.synchronizedMap()解析

    我们看源码:

      public int size() {
                synchronized (mutex) {return m.size();}
            }
            public boolean isEmpty() {
                synchronized (mutex) {return m.isEmpty();}
            }
            public boolean containsKey(Object key) {
                synchronized (mutex) {return m.containsKey(key);}
            }
            public boolean containsValue(Object value) {
                synchronized (mutex) {return m.containsValue(value);}
            }
            public V get(Object key) {
                synchronized (mutex) {return m.get(key);}
            }
    
            public V put(K key, V value) {
                synchronized (mutex) {return m.put(key, value);}
            }
            public V remove(Object key) {
                synchronized (mutex) {return m.remove(key);}
            }
            public void putAll(Map<? extends K, ? extends V> map) {
                synchronized (mutex) {m.putAll(map);}
            }
            public void clear() {
                synchronized (mutex) {m.clear();}
            }
    

    我们惊人的发现,synchronizedMap只是将HashMap的操纵放在了同步代码块中来保证SynchronizedMap的线程安全性。因此,SynchronizedMap也可以允许key和value为null。这样带来的问题也是任何一个时刻只能有一个线程可以操纵synchronizedMap,所以其效率比较低

    同样,Collections下的SynchronizedXX也是用同样方法实现线程安全性的。如SynchronizedSortedMap

    四、ConcurrentHashMap

    为什么ConcurrentHashMap可以多线程访问呢?是因为ConcurrentHashMap将Map分段了,每个段进行加锁,而不是想Hashtable,SynchronizedMap是整个map加锁,这样就可以多线程访问了。
    如图:
    在这里插入图片描述

    ConcurrentHashMap默认运行16个线程同时访问该map。但是我们可以通过一个函数来设置增加或减少最大可运行访问的线程数目。

    //就是这个concurrencyLevel
    public ConcurrentHashMap(int initialCapacity,
                                 float loadFactor, int concurrencyLevel)
    

    所以就有了下面这几个问题:
    (1) 多线程可以同时写一个ConcurrentHashMap的分段吗?

    不行。分段就像一个单独的HashMap,只允许一个线程向其写入数据

    (2)多个线程可以同时写入不同分段吗?

    这当然可以咯!

    (3)多个线程可以同时从一个分段中读数据吗?

    可以

    (4)如果一个线程正在向一个分段写入数据,其他线程可以从分段中读取数据吗?

    可以。但是读取到最后更新的数据。

    最后需要注意的一点是CoucrrentHashMap是不允许key和vlaue为null的。

    五、ArrayList为什么是线程不安全的,Vector是线程安全的?

    Vector肯定是做了同步控制了的。看源码

    //同步方法
     public synchronized void addElement(E obj) {
            modCount++;
            ensureCapacityHelper(elementCount + 1);
            elementData[elementCount++] = obj;
        }
    //同步方法
    public synchronized boolean removeElement(Object obj) {
            modCount++;
            int i = indexOf(obj);
            if (i >= 0) {
                removeElementAt(i);
                return true;
            }
            return false;
        }
    

    所以Vector也是用同步方法的方式来保证线程安全的。

    参考文献

    https://codepumpkin.com/hashtable-vs-synchronizedmap-vs-concurrenthashmap/

    展开全文
  • HashMap 线程安全问题

    万次阅读 多人点赞 2019-03-21 21:55:33
    我们紧接着上节ArrayList 线程安全问题讲下HashMap线程安全问题. 之前看书,书中经常会提及.HashTable是线程安全的,HashMap是线程非安全的.在多线程的情况下, HashMap会出现死循环的情况.此外,还会推荐使用新的JUC...

    前言

    我们紧接着上节ArrayList 线程安全问题讲下HashMap的线程安全问题.

    之前看书,书中经常会提及.HashTable是线程安全的,HashMap是线程非安全的.在多线程的情况下, HashMap会出现死循环的情况.此外,还会推荐使用新的JUC类 ConcurrentHashMap.

    今天,我们就将这些幺蛾子一网打尽. 本章, 将主要描述"为什么HashMap是非线程安全的? HashMap在多线程的情况下为什么会出现死循环?"


    正文 - 准备工作

    在讲解HashMap类的非线程安全问题之前, 我们需要对于HashMap的数据结构要有所了解.

    通过Eclipse的outline视图,我们瞄一瞄HashMap的源码内的对象.
    在这里插入图片描述
    通过观察,我们可以发现.HashMap主要是维护了一个Enrty类型的数组来存储变量.
    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;,其中transient是用于表示不需要序列化的标示,我们不用管他.

     static class Entry<K,V> implements Map.Entry<K,V> {
            final K key;
            V value;
            Entry<K,V> next;
            int hash;
     }
    

    我们在看下Entry类的源码.通过上述的Entry源码,我们可以发现它主要包括几个部分:key/value/hash/next.
    JDK1.7中的HashMap结构大致如下:
    在这里插入图片描述
    其中的每一块值,是一个Entry类型的链表.(在JDK1.8 之后,当Entry链表的长度多于一定的值的时候,会将其转换为红黑树. PS: 为了在O(log2N)的时间内获取到结点的值. 在JDK1.8内的红黑树也是使用链表进行实现的,不必过于在意.)


    单线程resize操作

    首先, 与ArrayList类一样, 我们先瞄一瞄HashMap的put方法.

    public V put(K key, V value) {
            if (table == EMPTY_TABLE) {
            	// 如果table为空的table,我们需要对其进行初始化操作.
                inflateTable(threshold);
            }
            if (key == null)
            	// 如果key为null的话 我们对其进行特殊操作(其实是放在table[0])
                return putForNullKey(value);
            // 计算hash值
            int hash = hash(key);
            // 根据hash值获取其在table数组内的位置.
            int i = indexFor(hash, table.length);
            // 遍历循环链表(结构图类似上图)
            for (Entry<K,V> e = table[i]; e != null; e = e.next) {
                Object k;
                // 如果找到其值的话,将原来的值进行覆盖(满足Map数据类型的特性.)
                if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                    V oldValue = e.value;
                    e.value = value;
                    e.recordAccess(this);
                    return oldValue;
                }
            }
    		// 如果都没有找到相同都key的值, 那么这个值是一个新值.(直接进行插入操作.)
            modCount++;
            addEntry(hash, key, value, i);
            return null;
        }
    

    跟随其后,我们深入addEntry(hash, key, value, i);方法一探究竟吧.

        void addEntry(int hash, K key, V value, int bucketIndex) {
            if ((size >= threshold) && (null != table[bucketIndex])) {
            	// 如果其大小超过threshold 并且table[index]的位置不为空
            	// 扩充HashMap的数据结果为原来的两倍
                resize(2 * table.length);
                hash = (null != key) ? hash(key) : 0;
                bucketIndex = indexFor(hash, table.length);
            }
    
            createEntry(hash, key, value, bucketIndex);
        }
    

    PS: mountCount/size/threshold的联系与区别?

    紧随其后,进入人们经常说的resize()方法.

        void resize(int newCapacity) {
            Entry[] oldTable = table;
            int oldCapacity = oldTable.length;
            if (oldCapacity == MAXIMUM_CAPACITY) {
            	//capacity 容量
                threshold = Integer.MAX_VALUE;
                return;
            }
    		// 创建一个新的对象
            Entry[] newTable = new Entry[newCapacity];
            // 将旧的对象内的值赋值到新的对象中
            transfer(newTable, initHashSeedAsNeeded(newCapacity));
            table = newTable;
            // 重新赋值新的临界点
            threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
        }
        
        void transfer(Entry[] newTable, boolean rehash) {
        	// 新table的长度
            int newCapacity = newTable.length;
            for (Entry<K,V> e : table) {
                while(null != e) {
                    Entry<K,V> next = e.next;
                    if (rehash) {
                        e.hash = null == e.key ? 0 : hash(e.key);
                    }
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                }
            }
        }
    

    这么一看,主要的resize()方法的转换操作都在transfer()方法内.我们详细的画图了解下这个循环.

    1. for (Entry<K,V> e : table) {}外层循环,循环遍历旧table的每一个值.这不难理解,因为要将旧table内的值拷贝进新table.
    2. 链表拷贝操作.主要是四行代码.e.next = newTable[i]; newTable[i] = e; e = next;这个操作一下子比较难懂,我们画图了解一下.
      在这里插入图片描述
      这个图还是有点抽象 我们再将细节进行细分.

    在这里插入图片描述
    1 next = e.next,目的是记录下原始的e.next的地址.图上为Entry-1-1.
    2 e.next = newTable[i],也就是将newTable[3]替代了原来的entry1-1.目的是为了记录原来的newTable[3]的链表头.
    3 newTable[i] = e,也就死将entry1-0替换成新的链表头.
    4. e = next;,循环遍历指针.将e = entry1-1.开始处理entry1-1.
    将步骤细分后,我们可以得到如下:
    在这里插入图片描述
    由上述的插入过程,我们可以看出.这是一个倒叙的链表插入过程.
    (比如 1-0 -> 1-1 ->1-2 插入后将变为 1-2 -> 1-1 -> 1-0)


    多线程操作 - 分析线程安全?线程非安全?

    因为倒叙链表插入的情况,导致HashMapresize()的情况下,导致链表出现环的出现.一旦出现了环那么在while(null != p.next){}的循环的时候.就会出现死循环导致线程阻塞.那么,多线程的时候,为什么会出现环状呢?让我们看下面的例子:

    首先有2个线程,由上一节我们知道,在resize()的过程中,一共有如下四个步骤:

    • Entry<K,V> next = e.next;
    • e.next = newTable[i];
    • newTable[i] = e;
    • e = next;

    我们有两个线程,线程A与线程B.线程A在执行Entry<K,V> next = e.next;之后,也就是第一个步骤之后,线程阻塞停止了.线程B之间进行了重排序的操作.此时的HashMap内部情况如下所示:

    • 初始(A阻塞 B运行完毕)
      在这里插入图片描述
    • A唤醒 e=key(3)
      A的执行步骤如下:
      • 线程阻塞前记录 next = key(7)
      • 线程唤醒后执行 newTable[3]=null; key(3).next=null;
      • newTable[3]=key(3);
      • e=next; 即 e = key(7);
        在这里插入图片描述
    • A唤醒 e=key(7)
      • next = key(3)
      • newTable[3]=key(3); key(7).next=key(3);
      • newTable[3]=key(7);
      • e=next; 即 e = key(3);
        在这里插入图片描述
    • A唤醒 e=key(3)
      • next = key(3).next; 即 next = null;
      • newTable[3]=key(7); key(3).next=key(7);
      • newTable[3]=key(3);
      • e=next; 即 e = null; 循环结束.但是此时已经形成了环路.
        在这里插入图片描述

    Tips

    链表出现环?
    快,慢指针方法.判断链表中是否有环 ----- 有关单链表中环的问题

    size、capacity、threshold与loadFactor?

    • size为hashMap内的链表结点数目.即Entry对象的个数(包括链表内).
    • capacity为桶的数目.(即Enrty<k,v> []table的长度) static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    • loadFactor 过载因子static final float DEFAULT_LOAD_FACTOR = 0.75f;
    • threshold 边界值.高于边界值,则HashMap进行resize操作.The next size value at which to resize (capacity * load factor). 定义int threshold;

    Reference

    [1]. HashMap 在 JDK 1.8 后新增的红黑树结构
    [2]. (1)美团面试题:Hashmap的结构,1.7和1.8有哪些区别,史上最深入的分析
    [3]. Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析
    [4]. HashMap?面试?我是谁?我在哪
    [5]. JDK7与JDK8中HashMap的实现
    [6]. 谈谈HashMap线程不安全的体现
    [7]. ConcurrentHashMap源码分析(JDK1.7和JDK1.8)
    [8]. HashMap中capacity、loadFactor、threshold、size等概念的解释

    展开全文
  • 线程HashMap的死循环

    千次阅读 2018-04-01 19:35:22
    多线程下HashMap的死循环Java的HashMap是非线程安全的。多线程下应该用ConcurrentHashMap。多线程下[HashMap]的问题(这里主要说死循环问题): 1、多线程put操作后,get操作导致死循环。 2、多线程put非NULL元素后...

    多线程下HashMap的死循环


    Java的HashMap是非线程安全的。多线程下应该用ConcurrentHashMap。

    多线程下[HashMap]的问题(这里主要说死循环问题):

    • 1、多线程put操作后,get操作导致死循环。

    • 2、多线程put非NULL元素后,get操作得到NULL值。

    • 3、多线程put操作,导致元素丢失。


    1、为何出现死循环?(在多线程下使用非线程安全的HashMap,单线程根本不会出现)

    • HashMap是采用链表解决Hash冲突,因为是链表结构,那么就很容易形成闭合的链路,这样在循环的时候只要有线程对这个HashMap进行get操作就会产生死循环。

    • 在单线程情况下,只有一个线程对HashMap的数据结构进行操作,是不可能产生闭合的回路的。

    • 那就只有在多线程并发的情况下才会出现这种情况,那就是在put操作的时候,如果size>initialCapacity*loadFactor,那么这时候HashMap就会进行rehash操作,随之HashMap的结构就会发生翻天覆地的变化。很有可能就是在两个线程在这个时候同时触发了rehash操作,产生了闭合的回路。


    2、如何产生的:
    存储数据put():

     

     

    	public V put(K key, V value)
    	{
    		......
    		//算Hash值
    		int hash = hash(key.hashCode());
    		int i = indexFor(hash, table.length);
    		//如果该key已被插入,则替换掉旧的value (链接操作)
    		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;
    				e.recordAccess(this);
    				return oldValue;
    			}
    		}
    		modCount++;
    		//该key不存在,需要增加一个结点
    		addEntry(hash, key, value, i);
    		return null;
    	}

    当我们往HashMap中put元素的时候,先根据key的hash值得到这个元素在数组中的位置(即下标),然后就可以把这个元素放到对应的位置中了。
    如果这个元素所在的位置上已经存放有其他元素了,那么在同一个位子上的元素将以链表的形式存放,新加入的元素放在链头,而先前加入的放在链尾。

    检查容量是否超标addEntry:

     

     

    	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);
    		//查看当前的size是否超过了我们设定的阈值threshold,如果超过,需要resize
    		if (size++ >= threshold)
    			resize(2 * table.length);
    	}

    如果现在size已经超过了threshold,那么就要进行resize操作,新建一个更大尺寸的hash表,然后把数据从老的Hash表中迁移到新的Hash表中。
    调整Hash表大小resize:

     

     

    	void resize(int newCapacity)
    	{
    		Entry[] oldTable = table;
    		int oldCapacity = oldTable.length;
    		......
    		//创建一个新的Hash Table
    		Entry[] newTable = new Entry[newCapacity];
    		//将Old Hash Table上的数据迁移到New Hash Table上
    		transfer(newTable);
    		table = newTable;
    		threshold = (int)(newCapacity * loadFactor);
    	}
    

    当table[]数组容量较小,容易产生哈希碰撞,所以,Hash表的尺寸和容量非常的重要。
    一般来说,Hash表这个容器当有数据要插入时,都会检查容量有没有超过设定的thredhold,如果超过,需要增大Hash表的尺寸,这个过程称为resize。
    多个线程同时往HashMap添加新元素时,多次resize会有一定概率出现死循环,因为每次resize需要把旧的数据映射到新的哈希表,这一部分代码在HashMap#transfer() 方法,如下:

     

     

    	void transfer(Entry[] newTable)
    	{
    		Entry[] src = table;
    		int newCapacity = newTable.length;
    		//下面这段代码的意思是:
    		//  从OldTable里摘一个元素出来,然后放到NewTable中
    		for (int j = 0; j < src.length; j++) {
    			Entry<K,V> e = src[j];
    			if (e != null) {
    				src[j] = null;
    				do {
    					Entry<K,V> next = e.next;//取出第一个元素
    					int i = indexFor(e.hash, newCapacity);
    					e.next = newTable[i];
    					newTable[i] = e;
    					e = next;
    				} while (e != null);
    			}
    		}
    	}

    标红代码是导致多线程使用hashmap出现CUP使用率骤增,出现死循环,从而多个线程阻塞的罪魁祸首。


    3、图解HashMap死循环:


    正常的ReHash的过程(单线程):
    假设了我们的hash算法就是简单的用key mod 一下表的大小(也就是数组的长度)。
    最上面的是old hash 表,其中的Hash表的size=2, 所以key = 3, 7, 5,在mod 2以后都冲突在table[1]这里了。
    接下来的三个步骤是Hash表 resize成4,然后所有的<key,value> 重新rehash的过程。
        

     

    并发下的Rehash(多线程)
    1)假设我们有两个线程。

     

     

    	do {
    		Entry<K,V> next = e.next; // <--假设线程一执行到这里就被调度挂起了,执行其他操作
    		int i = indexFor(e.hash, newCapacity);
    		e.next = newTable[i];
    		newTable[i] = e;
    		e = next;
    	} while (e != null);

    而我们的线程二执行完成了。于是我们有下面的这个样子:
        

    注意,因为Thread1的 e 指向了key(3),而next指向了key(7),其在线程二rehash后,指向了线程二重组后的链表。我们可以看到链表的顺序被反转后。在这里线程一变成了操作经过线程二操作后的HashMap。

    2)线程一被调度回来执行。
    先是执行 newTalbe[i] = e;
    然后是e = next,导致了e指向了key(7),
    而下一次循环的next = e.next导致了next指向了key(3)。
            
    3)一切安好。
    线程一接着工作。把key(7)摘下来,放到newTable[i]的第一个,然后把e和next往下移。这个元素所在的位置上已经存放有其他元素了,那么在同一个位子上的元素将以链表的形式存放,新加入的放在链头,而先前加入的放在链尾。
            

    4)环形链接出现。
    e.next = newTable[i] 导致  key(3).next 指向了 key(7)。
    注意:此时的key(7).next 已经指向了key(3), 环形链表就这样出现了。
            
    于是,当我们的线程一调用到,HashTable.get(11)时,悲剧就出现了——Infinite Loop。

     

    这里介绍了在多线程下为什么HashMap会出现死循环,不过在真实的生产环境下,不会使用线程不安全的HashMap的。

    哈哈哈哈哈。

     

     

     

     

    参考资料:
    疫苗:Java HashMap的死循环


    每天努力一点,每天都在进步。

     

    展开全文
  • 如何使用线程安全HashMap

    千次阅读 2017-12-20 16:53:01
    HashMap为什么线程安全 导致HashMap线程安全的原因可能有两种: 1、当多个线程同时使用put方法添加元素的时候,正巧存在两个put的key发生了碰撞(根据hash值计算的bucket一样),那么根据HashMap的存储原理,这...
  • Golang无锁无线程安全HashMap,针对最快的读取访问进行了优化。 用法 为地图中的键设置值: m := &HashMap{} m.Set("amount", 123) 从地图中读取键的值: amount, ok := m.Get("amount") 使用地图来计数URL请求: ...
  • HashMap原理相同,但是是线程安全的,与HashMap的最大区别是HashMap的Key和Value可以为null,但是HashTable的Key和Value都不能为null。 但是HashTable是遗留类,最好不要使用。 ConcurrentHashMap Concurrent并发...
  • 如何让HashMap可以线程安全 HashMap 在并发执行 put 操作时会引起死循环,导致 CPU 利用率接近100%。 因为多线程会导致 HashMap 的 Node 链表形成环形数据结构,一旦形成环形数据结构,Node 的 next 节点永远不为空...
  • HashMap线程安全

    2020-07-02 15:53:34
    HashMap线程安全的吗,为什么不是线程安全的。 不是线程安全的,因为多线程环境下,使用 HashMap 进行 put 操作可能会引起死循环,导致 CPU 利用率接近 100%,所以在并发情况下 HashMap 不是线程安全的。 如果有...
  • HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key),由于非线程安全,效率上可能高于Hashtable。 HashMap允许将null作为一个entry的key...
  • Collections.synchronizedMap(hashMap)的线程安全:3. ConcurrentHashMap的线程安全: 3.1. ConcurrentHashMap 1.7: 3.2. ConcurrentHashMap 1.8: 0. HashMap是线程不安全的:    JDK1.8之前多线程...
  • 分析ConcurrentHashMap如何保证线程安全、ConcurrentHashMap的读写过程、Size()过程
  • 如何线程安全的使用HashMap

    万次阅读 2016-09-02 13:37:32
    在周二面试时,一面的面试官有问到HashMap是否是线程安全的,如何在线程安全的前提下使用HashMap,其实也就是HashMap,Hashtable,ConcurrentHashMap和synchronized Map的原理和区别。当时有些紧张只是简单说了下...
  • 思路:通过多个线程向同一个HashMap或HashTable插入一个key值完全相同的key值,最后在线程都执行完后打印HashMap或HashTable的大小.size ...所以说 HashTable 是线程安全 HashMap不安全 代码 package co...
  • 我们知道hashMap线程是不安全的,一般而言,我们怎么创建线程安全HashMap呢? 2 解决办法 我们可以使用Collections.synchronizedMap来创建HashMap,如下 static Map<String, String> results = ...
  • 一、为什么HashMap线程安全? (1)内部存储结构:HashMap内部存储使用了一个Node数组(默认大小是16),如果存在相同的hashcode和相同的key的元素,那么新值覆盖原来的旧值; 如果存在相同的hashcode,那么他们的...
  • 线程安全HashMap  在多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。例如,执行如下代码会引起死循环。  HashMap在并发执行put操作时会...
  • 众所周知,HashMap是线程不安全的。但是如果需要一个线程按钮的...如下代码就是线程安全HashMap public static Map m = Collections.synchronizedMap(new HashMap说白了,就是给HashMap加锁,jdk参考代码如下
  • 接触过HashMap的人应该对线程安全问题都不陌生,就算是没踩过多线程下HashMap的坑,起码在学习的过程中应该也听说过是非线程安全的,几乎你问每一个程序员hashmap是不是线程安全的,大家都会告诉不是的,那么我来从...
  • 进入正题,在周二面试时,一面的面试官有问到 HashMap 是否是线程安全的,如何在线程安全的前提下使用 HashMap,其实也就是 HashMap,Hashtable,ConcurrentHashMap 和 synchronized Map 的原理和区别。当时...
  • ArrayList和Vector有什么区别?HashMap和HashTable有什么区别?StringBuilder和StringBuffer有什么区别?...HashMap是非线程安全的,HashTable是线程安全的;StringBuilder是非线程安全的,StringBuff
  • HashMap 线程安全

    千次阅读 2012-01-05 21:32:29
    在平时开发中,我们经常采用HashMap来作为本地...但是,最近发现,HashMap并不是线程安全的,如果你的单例类没有做代码同步或对象锁的控制,就可能出现异常。 首先看下在多线程的访问下,非现场安全的HashMap的表
  • 从JDK1.2起,就有了HashMap,正如前一篇文章所说,HashMap不是线程安全的,因此多线程操作时需要格外小心。 在JDK1.5中,伟大的Doug Lea给我们带来了concurrent包,从此Map也有安全的了。 ConcurrentHashMap具体是...
  • 保证HashMap线程安全

    2019-07-25 15:57:27
    HashMap基于哈希表的Map接口的实现。此实现提供所有可选的映射操作,并允许使用null值和null键。... 值得注意的是HashMap不是线程安全的,如果想要线程安全HashMap,可以通过Collections类的静态方法sync...
  • hashmap 线程安全问题分析

    千次阅读 2018-06-23 23:48:44
    1.问题引入 开发过程使用了HashMap全局变量作为缓存 ...Hashmap是非线程安全的集合类,在此场景中RW分属于两个不同线程,会存在读写数据不一致性问题。比如W线程正在更新HashMap过程中,R线程同时读取HashMap,由...
  • Java HashMap 是非线程安全的。在多线程条件下,容易导致死循环,具体表现为CPU使用率100%。因此多线程环境下保证 HashMap线程安全性,主要有如下几种方法: 使用 java.util.Hashtable 类,此类是线程安全的。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 149,926
精华内容 59,970
关键字:

线程安全hasmap