目录
一、java 的集合框架有哪些?
二、说-下 ArrayList 和 LinkedList?
三、HashSet和TreeSet的区别?
四、HashMap 的数据结构是什么?
五、CAS机制
六、AQS理解
一、java 的集合框架有哪些?
Collection 是 Java 集合框架中的一个根接口,位于 java.util 包中。它代表了一组对象,即它是一个对象的集合。
二、说-下 ArrayList 和 LinkedList?
ArrayList 是最常用的 List 实现类, 内部是通过数组实现的, 它允许对元素进行快速随机访问。数 组的缺点是每个元素之间不能有间隔, 当数组大小不满足时需要增加存储能力,就要将已经有数 组的数据复制到新的存储空间中。当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进 行复制、移动、代价比较高。因此, 它适合随机查找和遍历, 不适合插入和删除。
LinkedList 是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较 慢。另外,他还提供了List 接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆 栈、队列和双向队列使用。
- 数组实现List:数组是一种连续的内存空间,通过索引可以快速地访问任意位置的元素,这符合 List 接口中按索引访问元素的需求。此外,数组可以通过动态扩容来支持动态增长的集合。
- 链表实现LinkedList:链表通过节点间的引用关系来组织数据,这种结构使得在链表的任意位置插入或删除元素变得非常简单和高效,而不需要移动其他元素。这符合 List 接口中要求支持动态添加、删除和访问元素的需求。
三、HashSet和TreeSet的区别?
HashSet:元素的哈希值是通过元素的hashcode 方法来获取的, HashSet 首先判断两个元素的哈希值,如果哈希值一样, 接着会比较equals 方法 如果 equls 结果为 true ,HashSet 就视为同一个元素。如果 equals 为 false 就不是 同一个元素。
TreeSet()是使用二叉树的原理对新 add()的对象按照指定的顺序排序(升序、降序), 每增 加一个对象都会进行排序,将对象插入的二叉树指定的位置。
Integer 和 String 对象都可以进行默认的 TreeSet 排序, 而自定义类的对象是不可以的, 自己定义的类必须实现 Comparable 接口,并且覆写相应的 compareTo()函数,才可以正常使用。
HashSet
无序:不保证元素的插入顺序。
唯一性:通过equals()方法确保集合中元素的唯一性,允许一个null元素。
性能:在添加、删除和查找操作上通常比TreeSet快,时间复杂度接近O(1)。
用途:适用于需要快速查找且不要求排序的场合。
TreeSet
有序:根据元素的自然顺序或构造时指定的Comparator进行排序。
唯一性:通过compareTo()方法(或Comparator的compare方法)确保集合中元素的唯一性,不允许null元素。
性能:在添加、删除和查找操作上的时间复杂度为O(log n),因为基于红黑树实现。
用途:适用于需要保持元素排序的场合。
四、HashMap 的数据结构是什么?
底层是采用数组结构来存储数据元素,默认长度16,通过put方法去添加数据hashmap会根据key的hash值进行取模运算,最终把值保存到数组的一个指定位置,这样会存在hash冲突:两个不同的hash值的key,最终取模会落到同一个数组下标,所以引入了链式寻址法来解决hash冲突,对应存在冲突的key,hashmap 把这些key 组成一个单项链表,然后采用尾插法 把key保存到链表的一个尾部,另外为了避免链表长度过长,导致我的查询效率下降,所以当链表长度大于8,并且数组长度大于64,hashmap会把当前链表转换为红黑树,从而减少链表数据查询的时间复杂度,来提升查询效率。
HashMap 是线程安全的吗?为什么?
- 非同步操作:HashMap的操作不是线程同步的。在多线程环境下,如果有多个线程同时对HashMap进行读写操作,就可能导致数据不一致的问题。因为HashMap没有采取任何措施来同步这些操作,所以无法保证操作的原子性和可见性。
- 非原子性操作:HashMap的某些操作(如put()方法)包含多个步骤,如计算哈希值、查找或插入元素等。这些步骤如果由不同的线程并发执行,就可能因为中间状态的可见性问题而导致数据不一致。
- 容量扩容:当HashMap中的元素数量超过其容量的一定比例时,HashMap会进行扩容操作。这个过程包括重新计算元素的哈希值、重新分配存储位置,并复制原数组中的元素到新数组中。如果在这个过程中有其他线程对HashMap进行修改,就可能导致数据丢失或出现异常。
知道线程安全的 Map 有哪些?
hashtable:在put、get、containsKey方法上添加synchronized关键字来保证线程安全
ConcurrentHashMap
ConcurrentHashMap 为什么是线程安全的?
整体架构:
jdk1.8 由数组+红黑树+单向链表构成,默认初始化长度16的数组,由于核心仍是hashmap,必然会存在hash冲突, 所有concurrenthashmap 采用链式寻址的方式解决hash 冲突,当hash冲突比较多的时候,会造成链表长度问题, 1.8引入红黑树,当数组长度大于64,并且链表长度大于8,单向链表会转化为红黑树。当链表长度小于8,红黑树就会退化为单项链表
基本功能:在hashmap基础上提供了并发安全的实现,通过对于node节点去加锁,来保证数据更新的安全性,性能方面优化
jdk1.8 concurrent。。。 的锁粒度是数组中某一个节点,
jdk1.7 锁定的是segment 锁的范围要更大,性能上会更低,
引入红黑树,降低数据查询的时间复杂度
当数组长度不够的时候,对数组进行扩容, 引入了多线程并发扩容(多个线程对原始数组进行分片),每个线程负责对一个分片的迁移。
五、CAS机制
cas 是java中unsafe类里面的一个方法,全称:CompareAndSwap 比较并交换
主要功能: 保证在多线程环境下,对于共享变量修改的一个原子性
例子: 有一个成员变量state默认值为0 ,定义了一个方法,逻辑为,先判断state 是否为0,如果为0 就修改为1,在单线程没有任何问题,多线程下会存在原子性问题
是一个 read -write 操作, 一般解决会在方法 加 synchronized同步锁来解决,但 是加锁 会带来性能上的损耗, 所以可以采用cas 机制进行优化 调用 compareandswaplint
六、AQS理解
AQS是多线程同步器,提供了两种锁的机制
排它锁:存在多个线程去竞争同一共享资源的时候,同一时刻只允许一个线程去访问这样一个共享资源,也就是多个线程中只能有一个线程去获得一个锁资源,比如 lock中的Reentrantlock重入锁,就是用到了aqs中排它锁功能
共享锁:也称为读锁,同一时刻允许多个线程同时获得锁的资源
aqs作为互斥锁 需要解决三个核心问题
6.1、互斥变量的设计 如何保证多线程同时更新互斥变量的时候线程的安全性
采用int 类型的互斥变量 state,用来记录锁竞争的状态 (0--没有 >=1 有线程持有)
6.2、线程获取锁资源 先会判断 state 是否为0 如果是 更新状态为1 表示占有到锁
如果多个线程同时做一个操作,导致线程安全性问题,aqs 采用cas机制保证state
互斥变量更新的一个原子性
6.3、未竞争到锁资源的线程等待 以及竟遭到锁的资源释放锁之后的唤醒
为获取到的线程 通过unsafe类中的park方法,去进行阻塞,把阻塞的线程按照先进先出的原则去加入到一个双向链表的一个结构中当获取到锁资源的线程释放锁之后,会从双向链表的头部去唤醒下一个等待的线程,再去竞争锁