除了加锁,Hashtable和HashMap还有不同吗?

如何保证线程安全?
一般有三种方式来代替原生的线程不安全的 :
1)使用 java.util. 类的方法包装一下 ,得到线程安全的 ,其原理就是对所有的修改操作都加上。方法如下:
public static Map synchronizedMap(Map m)
2)使用线程安全的类代替,该类在对数据操作的时候都会上锁,也就是加上
3)使用线程安全的类代替,该类在 JDK 1.7 和 JDK 1.8 的底层原理有所不同,JDK 1.7 采用数组 + 链表存储数据,使用分段锁保证线程安全;JDK 1.8 采用数组 + 链表/红黑树存储数据,使用 CAS +保证线程安全 。
不过**前两者的线程并发度并不高,容易发生大规模阻塞,**所以一般使用的都是 ,他的性能和效率明显高于前两者 。
具体是如何保证线程安全?
一般我们会使用来创建一个线程安全的Map
Map m = Collections.synchronizedMap(new HashMap(...));
中的这个静态方法其实是创建了一个内部类的对象,这个内部类就是。在其**内部维护了一个普通的 Map 对象以及互斥锁 mutex,**如下图所示:
可以看到有两个构造函数,如果你传入了互斥锁 mutex 参数,就使用我们自己传入的互斥锁 。如果没有传入,则将互斥锁赋值为 this,也就是将调用了该构造函数的对象作为互斥锁,即我们上面所说的 Map 。
创建出对象之后,通过源码可以看到对于这个对象的所有操作全部都是上了悲观锁的:
由于多个线程都共享同一把互斥锁,导致同一时刻只能有一个线程进行读写操作,而其他线程只能等待,所以虽然它支持高并发,但是并发度太低,多线程情况下性能比较低下 。
而且,大多数情况下,业务场景都是**读多写少,**多个线程之间的读操作本身其实并不冲突,所以 极大的限制了读的性能 。
所以多线程并发场景我们很少使用。
为什么不使用
和一样,也是非常粗暴的给每个方法都加上了悲观锁 ,我们随便找几个方法看看:
除了加锁,和还有不同吗?
Hash table的key和value不支持null,但是是支持key和value为null的
1)如果我们 put 了一个 value 为 null 进入 Map,会直接抛空指针异常:
2)如果我们put一个key为null进入Map,在调用下面这个方法时就会报错,因为我们使用null值去调用方法了
那么为什么支持key和value为null呢?
1) 相比做了一个特殊的处理,如果我们put进来的key是null,那么在计算这个key的hash值时会直接返回0
也就是说中 key为 null 的键值对的 hash 为 0 。因此一个对象中只会存储一个 key 为 null 的键值对,因为它们的 hash 值都相同 。
2)因为不会对put进来的value作检验,所以如果我们put进来的value值为null也没关系,因此一个对象可以存储多个value为null的键值对
但是有一个点要注意,并不是说调用get方法返回null就代表这个值在里,我们来看一下源码
如果 Map 中没有查询到这个 key 的键值对,那么 get 方法就会返回 null 对象 。但是我们上面刚刚说了,里面可以存在多个 value 为 null 的键值对,也就是说,通过 get(key) 方法返回的结果为 null 有两种可能:
1)没有查询到对应的键值对,返回null
2) 中这个 key 对应的 value 为 null
因此我们不能使用get方法来判断是否存在某个key,而是应该使用
方法 。
为什么不支持 key 和 value 为 null 呢?
不仅仅不支持key和value为null,也不支持,作为支持并发的容器,如果它们像一样,允许 null key 和 null value 的话,在多线程环境下会出现问题 。