怎样实现两个线程共享一个集合_你真的了解Java集合吗?

Java集合是我认为在Java基础中最最重要的知识点了,Java集合是必须掌握的。我在面试的时候,只要是面到Java,那一定是少不了Java集合。

b9f3665def855cc1b2ff54ec4c6beafd.png


java中集合大家族的成员实在是太丰富了,有常用的ArrayList、HashMap、HashSet,也有不常用的Stack、Queue,有线程安全的Vector、HashTable,也有线程不安全的LinkedList、TreeMap等等!
上面的图展示了整个集合大家族的成员以及他们之间的关系。下面就上面的各个接口、基类做一些简单的介绍(主要介绍各个集合的特点。区别)。
List集合 List接口为Collection直接接口。List所代表的是有序的Collection,即它用某种特定的插入顺序来维护元素顺序。用户可以对列表中每个元素的插入位置进行精确地控制,同时可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。实现List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。
List集合下最常见的集合类有两个:ArrayList和LinkedList
ArrayList ArrayList 以数组作为存储结构,它是线程不安全的集合;具有查询快、在数组中间或头部增删慢的特点,所以它除了线程不安全这一点,其余可以替代Vector,而且线程安全的 ArrayList 可以使用 CopyOnWriteArrayList代替 Vector。

fde7b358d74ddf032b5e16fc9683fa6d.png

关于 ArrayList 有几个重要的点需要注意的:
具备随机访问特点,访问元素的效率较高,ArrayList 在频繁插入、删除集合元素的场景下效率较低。
底层数据结构:ArrayList 底层使用数组作为存储结构,具备查找快、增删慢的特点
线程安全性:ArrayList 是线程不安全的集合
ArrayList 首次扩容后的长度为 10,调用 add() 时需要计算容器的最小容量。可以看到如果数组elementData为空数组,会将最小容量设置为10,之后会将数组长度完成首次扩容到 10。
集合从第二次扩容开始,数组长度将扩容为原来的 1.5 倍,即:newLength = oldLength * 1.5

8920870c0dbdb464c7042dd07852e082.png


LinkedList LinkedList 底层采用双向链表数据结构存储元素,由于链表的内存地址非连续,所以它不具备随机访问的特点,但由于它利用指针连接各个元素,所以插入、删除元素只需要操作指针,不需要移动元素,故具有增删快、查询慢的特点。它也是一个非线程安全的集合。

ff7f367fc3fec8414b6fad68bbd80852.png

由于以双向链表作为数据结构,它是线程不安全的集合;存储的每个节点称为一个Node,下图可以看到 Node 中保存了next和prev指针,item是该节点的值。在插入和删除时,时间复杂度都保持为 O(1)
关于 LinkedList,除了它是以链表实现的集合外,还有一些特殊的特性需要注意的。
优势:LinkedList 底层没有扩容机制,使用双向链表存储元素,所以插入和删除元素效率较高,适用于频繁操作元素的场景 劣势:LinkedList 不具备随机访问的特点,查找某个元素只能从 head 或 tail 指针一个一个比较,所以查找中间的元素时效率很低 查找优化:LinkedList 查找某个下标 index 的元素时做了优化,若 index > (size / 2),则从 head 往后查找,否则从 tail 开始往前查找 双端队列:使用双端链表实现,并且实现了 Deque 接口,使得 LinkedList 可以用作双端队列。下图可以看到 Node 是集合中的元素,提供了前驱指针和后继指针,还提供了一系列操作头结点和尾结点的方法,具有双端队列的特性。Set集合 Set接口继承了Collection接口,是一个不包括重复元素的集合,更确切地说,Set 中任意两个元素不会出现 o1.equals(o2),而且 Set 至多只能存储一个 NULL 值元素,Set 集合的组成部分可以用下面这张图概括:

1c8b3b7091f7006c0d6f487925860f43.png

在 Set 集合体系中,我们需要着重关注两点:
存入可变元素时,必须非常小心,因为任意时候元素状态的改变都有可能使得 Set 内部出现两个相等的元素,即 o1.equals(o2) = true,所以一般不要更改存入 Set 中的元素,否则将会破坏了 equals() 的作用!
Set 的最大作用就是判重,在项目中最大的作用也是判重!
HashSet HashSet 底层借助 HashMap 实现,我们可以观察它的多个构造方法,本质上都是 new 一个 HashMap
底层数据结构:HashSet 也是采用数组 + 链表 + 红黑树实现 线程安全性:由于采用 HashMap 实现,而 HashMap 本身线程不安全,在HashSet 中没有添加额外的同步策略,所以 HashSet 也线程不安全 存入 HashSet 的对象的状态最好不要发生变化,因为有可能改变状态后,在集合内部出现两个元素o1.equals(o2),破坏了 equals()的语义。
TreeSet TreeSet 是基于 TreeMap 的实现,所以存储的元素是有序的,底层的数据结构是数组 + 红黑树。

24f9f04a82fcf745d3bf8825633c014e.png

而元素的排列顺序有2种,和 TreeMap 相同:自然排序和定制排序,常用的构造方法已经在下面展示出来了,TreeSet 默认按照自然排序,如果需要定制排序,需要传入Comparator。
对 TreeSet 介绍了它的主要实现方式和应用场景,有几个值得注意的点:
TreeSet 的所有操作都会转换为对 TreeMap 的操作,TreeMap 采用红黑树实现,任意操作的平均时间复杂度为 O(logN) TreeSet 是一个线程不安全的集合 TreeSet 常应用于对不重复的元素定制排序,例如玩家战力排行榜 Map集合
Map集合最常见的子类也有三个:HashMap、LinkedHashMap、TreeMap
如果考虑线程安全问题,应该想到的是ConcurrentHashMap,当然了Hashtable也要有一定的了解,因为面试实在是问得太多太多了。
HashMap在实际开发中用得也非常多,只要是key-value结构的,一般我们就用HashMap。LinkedHashMap和TreeMap用的不多,原因跟HashSet和TreeSet一样。
HashMap HashMap 是一个最通用的利用哈希表存储元素的集合,将元素放入 HashMap 时,将key的哈希值转换为数组的索引下标确定存放位置,查找时,根据key的哈希地址转换成数组的索引下标确定查找位置。
HashMap 底层是用数组 + 链表 + 红黑树这三种数据结构实现,它是非线程安全的集合。
发送哈希冲突时,HashMap 的解决方法是将相同映射地址的元素连成一条链表,如果链表的长度大于8时,且数组的长度大于64则会转换成红黑树数据结构。
关于 HashMap 的简要总结:
它是集合中最常用的Map集合类型,底层由数组 + 链表 + 红黑树组成 HashMap不是线程安全的 插入元素时,通过计算元素的哈希值,通过哈希映射函数转换为数组下标;查找元素时,同样通过哈希映射函数得到数组下标定位元素的位置 LinkedHashMap LinkedHashMap 可以看作是 HashMap 和 LinkedList 的结合:它在 HashMap 的基础上添加了一条双向链表,默认存储各个元素的插入顺序,但由于这条双向链表,使得 LinkedHashMap 可以实现 LRU缓存淘汰策略,因为我们可以设置这条双向链表按照元素的访问次序进行排序
关于 LinkedHashMap 主要介绍两点:
它底层维护了一条双向链表,因为继承了 HashMap,所以它也不是线程安全的 LinkedHashMap 可实现LRU缓存淘汰策略,其原理是通过设置accessOrder为true并重写removeEldestEntry方法定义淘汰元素时需满足的条件 TreeMap TreeMap 是 SortedMap 的子类,所以它具有排序功能。它是基于红黑树数据结构实现的,每一个键值对都是一个结点,默认情况下按照key自然排序,另一种是可以通过传入定制的Comparator进行自定义规则排序。
关于 TreeMap 主要介绍了两点:
它底层是由红黑树这种数据结构实现的,所以操作的时间复杂度恒为O(logN) TreeMap 可以对key进行自然排序或者自定义排序,自定义排序时需要传入Comparator,而自然排序要求key实现了Comparable接口 TreeMap 不是线程安全的。
什么时候考虑线程安全 什么时候考虑线程安全的集合类,那当然是线程不安全的时候咯。那什么时候线程不安全?最常见的是:操作的对象是有状态的
虽然说,我们经常会听到线程不安全,但在业务开发中要我们程序员处理线程不安全的地方少之又少。比如说:你在写Servlet的时候,加过syn/lock锁吗?应该没有吧?
因为我们的操作的对象往往是无状态的。没有共享变量被多个线程访问,自然就没有线程安全问题了。
SpringMVC是单例的,但SpringMVC都是在方法内操作数据的,每个线程进入方法都会生成栈帧,每个栈帧的数据都是线程独有的,如果不设定共享变量,不会有线程安全问题。
上面只是简单举了SpringMVC的例子(只是为了更好的理解);
一句话总结:只要涉及到多个线程操作一个共享变量的时候,就要考虑是不是要用线程安全的集合类。
更多的细节,等我写Java多线程总结的时候再说了
最后 这一篇文章对各个集合都有些点到即止的味道,此文的目的是对整个集合框架有一个较为整体的了解,分析了最常用的集合的相关特性。
本文对整个集合体系的所有常用的集合类都分析了,这里并没有对集合内部的实现深入剖析,我想先从最宏观的角度让大家了解每个集合的的作用,应用场景,以及简单的对比,之后会抽时间对常见的集合进行源码剖析,尽情期待,感谢阅读!
如果你想要去面试,Java集合是肯定少不了的,必问的一个知识点,你学会了就是送分题。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值