锁是Java中用来保证线程操作原子性的一种机制
锁是数据库中用来保证事务操作原子性的一种机制
Java中锁有synchronized和Lock锁
synchronized是关键字,可以锁代码块,也可以锁方法
Lock是类(官方推荐),只能锁代码块
我们把数据类型分为线程安全类型和线程不安全类型
如果一个数据类型需要我们自己手动加锁来保证其操作的原子性,那么它就是线程不安全的数据类型
如果一个数据类型能够自己在方法中加锁来保证其操作的原子性,那么它就是线程安全的数据类型
线程不安全 | 线程安全 |
ArrayList | 1、Vector 2、CopyOnWriteArrayList |
HashMap | 1、Hashtable 2、concurrentHashMap |
String,StringBuilder | StringBuffer |
int,Integer | AtomicInteger |
其他------------------------------------------------------------------------------------------- |
产生死锁的条件是什么(产生死锁的原因)
1、互斥条件:锁要具有排他性,在同一时刻,锁只能被一个线程持有
2、请求与保持条件:一个线程因为请求其他资源被阻塞时,对以获取的资源保持不放
3、不剥夺条件:一个线程没有主动释放资源之前是不能被其他线程强行剥夺的
4、循环等待条件:A线程持有资源a的锁,B线程持有资源b的锁,在互相不释放自己锁的情况下,去请求对方持有的锁,这时候会形成双方循环等待,造成永久阻塞
如何解决死锁
破坏任意一个条件即可
1、破坏互斥条件:用共享锁。在同一时刻,锁可以被多个线程持有
2、破坏请求与保持条件:一次性申请所有的资源
3、破坏不剥夺条件:一个线程因为请求其他资源被阻塞时,主动释放自己已获取的资源
4、破坏循环等待条件:所有的线程按照同样的顺序请求资源
线程池
集合容器的整理
Collection和Map的区别
- Collection和Map是官方提供的集合容器的两大体系的顶层接口
- Collection代表单元素集合体系
- Map代表kv键值对集合体系
- Collection体系继承了Iterable接口,所有的子类都提供了迭代器的实现,Map体系没有
List,Set,Queue的区别
- List,Set,Queue但是Collection体系下的子接口,分别代表三个体系
- List体系的特点是有序,不唯一
- Set体系的特点是无序,唯一
- Queue体系的特点是先入先出(队列)
队列(Queue和栈(Stack)的区别
1、队列是一种FIFO(First In First Out)先入先出的的结构
2、栈是一种FILO(First In Last Out)先入后出的结构
Java集合体系中的LinkedList类是可以实现队列和栈结构
在链表头部插入,尾部取出或者尾部插入,头部取出就是队列(插入和取出在不同的方向上进行)
在链表头部插入,头部取出或者尾部插入,尾部取出就是栈(插入和取出在相同的方向上进行)
HashSet和TreeSet的区别
HashSet和TreeSet都是Set接口下面的子类
HashSet的底层是HashMap,它将数据存储在HashMap的key中
HashSet是无序的,唯一的,因为HashMap的key是无序的,唯一的
TreeSet的底层是TreeMap,它将数据存储在TreeMap的key中
TreeSet是有序的,唯一的,因为TreeMap的key是有序的,唯一的
HashMap和Hashtable的区别(带一下ConcurrentHashMap)
HashMap是线程不安全的,Hashtable是线程安全的。HashMap中的所有方法都没有加同步锁,Hashtable中所有方法都加了synchronized同步锁。官方在JDK1.5版本中又推出了一个ConcurrentHashMap,使用了Lock锁实现线程安全,然后弃用Hashtable,因为Lock锁的性能比synchronized锁的性能好。
在并发编程中,如果多个线程共享一个HashMap,那么必须考虑线程安全的问题,可以自己在代码中对HashMap操作代码加锁,或者直接用线程安全的ConcurrentHashMap类,在不考虑线程安全的环境下,用HashMap性能更好,因为加锁和开锁是很消耗性能的。
对null key支持:HashMap支持key为null,但只能有一个,Hashtable不支持key为空,会直接抛NPE,HashMap和Hashtable支持value为空,不限制个数
ConcurrentHashMap的key和value都不支持为null
HashMap在1.8以后,设置了阈值 = 8,当链表长度超过阈值或数组长度超过64的时候,会转换为红黑树以减少检索时间,Hashtable被弃用了没有更新
初始容量大小和扩容大小的区别:
HashMap默认初始容量是16,扩容策略是原来的2倍
Hashtable默认的初始容量是11,扩容策略是原来的2n+1
HashMap如果手动指定了初始容量,不是2的n次方,它也会找到最近的一个2的n次方作为初始容量
Hashtable如果手动指定了初始容量,会直接使用指定的大小
Hash table采用了锁全表的机制,ConcurrentHashMap采用了分段锁的设计,锁粒度更细,性能更好
HashMap和TreeMap的区别
Hash Map底层是数组+链表/红黑树,key是无序的,唯一的
TreeMap底层是红黑树,key是有序的,唯一的
HashMap的性能比TreeMap更好,但如果需要一个有序的key的集合,需要使用TreeMap
HashMap的底层原理(数据结构+put()流层+resize()流程)
HashMap的底层实现,HashSet的底层实现
- HashMap在JDK1.8之前是数组+链表,JDK1.8之后是数组+链表/红黑树
- HashSet的底层是HashMap
HashMap的put方法的底层原理
1.根据key的HashCode计算出数组的索引(index)
2.落槽时
2.1如果数组中节点为null,创建新的节点对象,把k,v存储在节点对象中,把节点对象存储在数组中
2.2如果数组节点不为null,判断节点的key与插入元素的key是否相等
2.2.1相等,直接用新的k,v覆盖原节点中的k,v
2.2.2不相等,判断此时节点是否为红黑树
2.2.2.1是红黑树,创建红黑数节点对象存储k,v,插入到红黑树中
2.2.2.2不是红黑树,创建链表节点对象存储k,v,插入到链表中,判断链表长度是否大于阈值8
2.2.2.2.1大于阈值8,链表转换为红黑树
3、判断++size是否大于阈值,是就扩容
HashMap的resize()扩容方法的底层原理
- HashMap默认初始容量是16
- resize()方法是在hashmap中的size大于阈值时或者初始化时,就调用resize方法进行扩容每次扩容的时候始终是原数组长度的2倍,即长度永远是2的n次方
- 扩容后节点对象的位置要么在原位置,要么偏移到两倍的位置
HashMap为什么不直接使用key的hashCode()函数返回的哈希码作为落槽时的索引号
“hashCode()”方法返回的是int整数类型,其范围为-(2 ^ 31)~(2 ^ 31 - 1),约有40亿个映射空间,而HashMap的容量范围是在16(初始化默认值)~2 ^ 30,HashMap通常情况下是取不到最大值的,并且设备上也难以提供这么多的存储空间,从而导致通过“hashCode()”计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置
HashMap的长度为什么是2的幂次方
为了能让HashMap存取高效,尽量减少碰撞,也就是要尽量把数据分配均匀,,每个链表/红黑树长度大致相同,这个实现就是把数据存到哪个链表/红黑树中的算法
什么是哈希冲突/哈希碰撞,怎么解决哈希冲突,HashMap采用的是什么策略
如果有两个不同字符串通过同样的哈希算法计算出来的而哈希码是一样的,则它们发生哈希碰撞,哈希冲突
如何解决哈希冲突
1、开放地址法
2、拉链法(连地址法)HashMap默认使用的就是这种
那HashMap是怎么解决的呢
这题也可以这样问
HashMap的底层是如何计算key落槽时的索引的
1. HashMap自己实现了自己的“hash()”方法,通过两次扰动(例如再次哈希运算)使得它自己的哈希值高低位自行进行异或运算,降低哈希碰撞概率也使得数据分布更平均;
2. 在保证数组长度为2的幂次方的时候,使用`hash()`运算之后的值与运算(&)(数组长度 - 1)来获取数组下标的方式进行存储,这样一来是比取余操作更加有效率,二来也是因为只有当数组长度为2的幂次方时,h&(length-1)才等价于h%length,三来解决了“哈希值与数组大小范围不匹配”的问题
== 和 equals() 方法的区别
== 和 equals 都可以用于比较,语法是:a == b 或者 a.equals(b)
== 比较的是内存地址
equals()方法是Object类中方法,可以被任意类继承和重写,通过看官方Object原码知道equals()方法默认也是用 == 比较内存地址
如果想要修改equals()方法的比较规则,可以重写equals()方法
String类就重写equals()方法的比较规则,由默认的比较两个字符串对象的内存地址,修改为比较字符串中每个字符是否相等
因为堆区中可能出现两个一模一样的字符串对象,但内存地址不一样,所以字符串的比较必须equals()方法,否则可能会出现两个内容一模一样的字符串,因为
为什么重写了equals()方法,必须也要重写HashCode()方法
HashMap的底层采用了key的HashCode()来计算数组的索引index
如果数组[index]为null说明key不存在,直接落槽
如果数组[index]不为null说明该位置有key存在,但不能一定说明已经存在的key与要插入的key重复,因为可能会发生哈希碰撞,此时应该进一步用equals方法比较已经存在的key与要插入的key是否相等,如果相等就说明一定是重复的,应该覆盖,如果不相等说明发生了哈希碰撞,那么应该插入在链表中
重写equals()方法的目的是为了不去比较两个对象的内存地址,改为比较对象的内容,如果一个类重写了equals,没有重写hashcode就可能出现两个地址不同的对象equas比较相等,但是hashcode比较不相等,这样会违反hashcode的唯一性,因此,重写了equals方法必须也要重写hashcode方法,且必须满足两个对象equals相等,hashcode也必须相等
能否使用任意的数据类型作为HashMap的key
可以,前提是要重写比较方法equals(),同时还要重写hashcode()方法