优秀的编程知识分享平台

网站首页 > 技术文章 正文

从发展来看Vector与Hashtable的淘汰

nanyue 2024-08-12 22:28:38 技术文章 3 ℃

语言是会随着时代的发展而发展的,而Vector与Hashtable又是如何随着Java的发展而不再使用的呢?

同步容器

在Java发展的早期就在支持多线程了,也提供了一些线程安全的容器,比如在最早jdk1.2提供的集合容器类比如Vector、Hashtable,他们的实现机制都差不多,都是在方法层面上加synchronized关键字来实现线程安全。

在早期jdk还提供了Collections.synchronizedXxx系列方法来创建同步容器,具体如下图:

这几个方法的实现方式都差不多,都是把集合对象作为参数设置进一个新对象里,然后将传递进去的集合作为新建对象的属性封装起来,最后通过访问新建对象的方法(通过synchronized实现同步)去访问传递进去的集合。

这类通过锁实现同步的叫着同步容器,同步容器对于他自身来说是线程安全的,但是我们在进行一些复合操作的时候就可能并不是线程安全的了,比如"int size = vector.size(); vector.remove(size);"这类操作,在多线程情况下如果也有get最后一个元素,就很容易出现异常。

在比如"for (int i = 0; i < vector.size(); i++)"虽然vector是线程安全的,但是可以能在遍历的过程中其他线程对vector进行了操作,比如移除了最后一个,那么这个遍历就会出现异常。

那么要解决这类复合操作就必须额外加锁去控制。同步容器是通过把所有对容器的状态访问都通过加锁来实现串行化访问,最终实现容器的线程安全,这种方式的缺点就是严重降低了容器的并发性,在多线程竞争容器的锁时就会严重影响程序的性能。

并发容器

在jdk1.5开始提供了多种并发容器用来改进同步容器的性能,并发容器是针对多个线程并发访问设计,常见的比如ConcurrentHashMap来代替同步的Map系列,CopyOnWriteArrayList来代替同步的List系列,CopyOnWriteArraySet来代替同步的Set系列,这些并发容器还提供了一些复合操作,比如"没有则添加"、替换、删除;

先来简单看下并发容器是如何优化的,以ConcurrentHashMap为例,要想了解ConcurrentHashMap是如何优化的就必须先了解他的数据结构,在jdk8以前与hashtable这里画了一个简单的对比图,如下图:

hashtable底层是一个数组结构,数组每个元素又是一个链表结构,每次访问hashtable方法都会针对hashtable这个对象加锁,一次只能一个线程访问,而在ConcurrentHashMap把数据分成了多个Segment数组,在每个Segment中在保存一个HashEntry数组,HashEntry数组中元素又是一个链表结构,每次访问都是访问某个Segment元素下的数据,每次只针对对应的Segment加锁,也就是ConcurrentHashMap下有多少个Segment那么他就最多支持同时多少个线程访问。

在jdk8对ConcurrentHashMap进一步进行了优化,它的真正结构已经和hashmap差不多了,而且是针对每个node进行加锁,可以支持更高的并发,底层数组有多长就最多支持多大的并发。

同时ConcurrentHashMap还支持了一些复合操作,比如putIfAbsent、remove(Object key, Object value)(存在才删除)这类的更加安全的方法。


总结

经常看到一些资料在说Vector与Hashtable这类的不要使用,而是应该使用这样那样的容器,虽然知道是因为他们的实现机制不够好的原因,却很少知道是因为历史发展原因,出现了更加优化的容器。

在最开始只考虑到多线程的安全问题,所以实现了一堆同步容器,知道多线程、高并发越来越多,同步容器的性能影响了整体性能,所以才有了并发容器的发展。


Java程序员日常学习笔记,如理解有误欢迎各位交流讨论!

最近发表
标签列表