目录
HashMap
多线程环境中使用哈希表
HashMap不适合在多线程环境下使用,HashMap无法保证线程安全。因此不支持并发访问。在多线程下使用哈希表可以使用HashTable或者ConcurrentHashMap。
但是更推荐使用concurrentHashMap。
HashTable
hashTable底层也是基于哈希表实现的,HashTable是线程安全的,内部使用synchronized关键字加锁。相当于给this加锁。
给关键方法加上锁之后,虽然保证了在多线程环境下的使用不会出现线程安全问题,但是HashTable加锁的粒度太粗了。
hashTable是针对整个哈希表进行加锁的,任何增删改查的操作都是触发加锁,也就可能有锁竞争。
加锁的粒度不同,引发线程对锁的竞争就越高。
如果多个线程对同时需要操作HashTable的一个方法,那么此时就会有锁竞争,但是最终只能有一个线程获取锁对象。那么其他线程此时就只能阻塞等待了,当释放锁之后,剩余线程继续进行锁竞争和阻塞。
如果此时线程1给数组的0下标进行插入元素,线程2给数组的1下标进行插入元素,此时是不会出现线程安全的问题的,但是我们的HashTable的加锁机制是给整个哈希表进行加锁操作的。是针对this进行加锁的,所以此时就会有一个线程阻塞等待。这就导致了hashTable的效率非常的低。
ConcurrentHashMap
由于HashTable在多线程环境下表现并不是很出色,效率非常低。于是就出现了ConcurrentHashMap。
上述的HashTable是整个哈希表只有一把锁,这也就导致了效率的低下。但是ConcurrentHashMap
不是只要一把锁,而是一个链表的头结点有一把锁。
也就说只有是针对不同链表的进行操作,就不会出现锁竞争,也就不会阻塞等待。
这就导致大部分操作都是没有阻塞的。
上述情况是jdk1.8开始的。实际在1.8之前的时候,是采用分段锁的方式来实现加锁的,和上述情况相似,是采用好几个链表共用一把锁对象。
ConcurrentHashMap在其他方面的改进
采用CAS机制
比如一下获取元素个数/更新元素个数的操作就可以使用CAS了,也没有必要加锁。
CAS也能保证线程安全,而且比锁更高效。
优化了扩容机制
hashtable的扩容是当达到一定的负载因子的时候,扩容就需要重新申请空间,然后把旧的空间上的元素全部挪动到新的空间中。
如果元素要是非常多,此时的扩容就会非常的卡顿。
concurrentHashMap的扩容是采用化整为0的策略,不会视图一次将全部元素都诺过去,而是只挪动一小部分。
此时就有两个hash表了。
当删除元素的时候,就会查询要删除的元素在那个表,然后删除。
当添加元素的时候,就直接插入在新表中。
每次操作都会触发挪动,每次也都是挪动一小部分。
最终会将整个表都全部挪过去。