HashMap线程安全的吗?
Java中平时用的最多的Map集合就是HashMap了,它是线程不安全的。
看下面两个场景:
1、当用在方法内的局部变量时,局部变量属于当前线程级别的变量,其他线程访问不了,所以这时也不存在线程安全不安全的问题了。
2、当用在单例对象成员变量的时候呢?这时候多个线程过来访问的就是同一个HashMap了,对同个HashMap操作这时候就存在线程安全的问题了。
image2 线程安全的Map
为了避免出现场景2的线程安全的问题,不能使用HashMap作为成员变量,要寻求使用线程安全的Map,下面来总结下有哪些线程安全的Map呢?
1、HashTable
private Map<String, Object> map = new Hashtable<>();
来看看HashTable的源码
image imageHashTable的get/put方法都被synchronized关键字修饰,说明它们是方法级别阻塞的,它们占用共享资源锁,所以导致同时只能一个线程操作get或者put,而且get/put操作不能同时执行,所以这种同步的集合效率非常低,一般不建议使用这个集合。
2、SynchronizedMap
private Map<String, Object> map = Collections.synchronizedMap(new HashMap<String, Object>());
这种是直接使用工具类里面的方法创建SynchronizedMap,把传入进行的HashMap对象进行了包装同步而已,来看看它的源码。
image这个同步方式实现也比较简单,看出SynchronizedMap的实现方式是加了个对象锁,每次对HashMap的操作都要先获取这个mutex的对象锁才能进入,所以性能也不会比HashTable好到哪里去,也不建议使用。
3、ConcurrentHashMap - 推荐
private Map<String, Object> map = new ConcurrentHashMap<>();
这个也是最推荐使用的线程安全的Map,也是实现方式最复杂的一个集合,每个版本的实现方式也不一样,在jdk8之前是使用分段加锁的一个方式,分成16个桶,每次只加锁其中一个桶,而在jdk8又加入了红黑树和CAS算法来实现。
image虽然实现起来很复杂,但使用起来也是非常简单的,在java面试中问的频率也非常高,最重要的是性能要比上面两种同步方式要快太多,推荐使用。
hash冲突
如果两个不同的元素,通过哈希函数得出的实际存储地址相同怎么办?也就是说,当我们对某个元素进行哈希运算,得到一个存储地址,然后要进行插入的时候,发现已经被其他元素占用了,其实这就是所谓的哈希冲突,也叫哈希碰撞。前面我们提到过,哈希函数的设计至关重要,好的哈希函数会尽可能地保证 计算简单和散列地址分布均匀,但是,我们需要清楚的是,数组是一块连续的固定长度的内存空间,再好的哈希函数也不能保证得到的存储地址绝对不发生冲突。那么哈希冲突如何解决呢?哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址法,也就是数组+链表的方式
将对象放入到集合中时,首先判断放入的对象的hashcode是否已经在集合中存在, 如果不存在就直接放入集合。如果hashcode 相等,通过equl方法判断放入对象和集合的任意对象是否相等,如果equal方法判断不相等,直接将该元素放入集合,否则不放入。
Hashmap和Hashtable的区别?
两者都实现了Map接口,因此很多特性非常相似。但是他们有以下的不同点:
1 前者允许键和值是null,而Hashtable不允许键或值是null
2 Hashtable是同步的,而Hashmap不是。因此,HashMap更适合单线程环境,而Hashtable适合于多线程环境。
3 前者提供了可供应用迭代的建和值,因此,Hashmap是快速失败的的。另一方面,Hashtable提供了对键的列举(Enumeration)
网友评论