synchronized和Lock接口,在加锁时有什么区别1. synchronized是隐式加锁,lock是显示加锁

  1. synchronized是阻塞式加锁,lock是非阻塞式,支持可中断式加锁,支持超时时间加锁
  2. synchronized在加锁解锁时,只有一个同步队列和一个等待队列。

lock有一个同步队列,可以有多个等待队列

  1. synchronized只支持非公平锁,lock支持非公平锁和公平锁
  2. lock支持个性化定制,使用了模板方法模式,可以自行实现lock方法。

HashMap

如何定位下标

先获取key,对key进行hash,得到hash值,用hash值对HashMap的容量进行取余(实际上不是真的取余,而是使用按位与操作),最终得到下标

容量和负载因子

  1. HashMap的默认容量为16,负载因子默认为0.75,当元素超过阈值(容量*因子(即12))时,触发扩容,容量上限为2^30(即1073741824)。
  2. 初始化:HashMap初始化的值要是2的指数倍,当指定为10时,实际为2^4=16。
  3. 扩容:先创建一个原容量两倍大的map,接着遍历原map,将所有的元素都移到新map。
  4. 由于Map扩容时对性能的影响较大,建议在初始化时设定好Map的容量,以免反复扩容影响效率。

    hash值

    hash值:因为hash值根据余上长度减一计算的,所以扩容后会重新计算hash值。

    HashMap组成

    jdk1.7

    数组+单链表

    jdk1.8

    数组+单链表+红黑树
    当链表节点数超过8个以后,开始使用红黑树,使用红黑树是一个综合取优的选择,红黑树的查询和插入效率都比较高。当红黑树的节点数小于6个(默认值)以后,又开始使用链表。两个阈值不相同,主要为了防止出现节点个数频繁在一个相同的数值来回切换。举个例子:现在单链表的个数是9,开始变成红黑树,然偶红黑树节点又变成8,就又变成单链表,然后节点数又变成9,又变成红黑树,所以严重消耗性能。错开两个阈值的大小,使得变成红黑树之后不会轻易变成单链表,反之亦然。

    为什么不用取余的方式存取数据

    实际上HashMap的indexFor方法用的是跟HashMap的容量-1做按位与操作,而不是%求余

    容量大小取2的指数倍原因

  5. 提升计算效率。2的指数倍的二进制都只有一个1,而2的指数倍-1的二进制数就是左边全0右边全1,那么跟(2^n-1)做按位与运算的话,得到的值就一定在【0,(2^n-1)】区间内,这样的数就刚好合适用来作为哈希表的容量大小:往哈希表插入数据,就要对其容量大小取余,从而得到下标。所以用2^n作为容量大小的话,就可以用按位与操作代替取余操作,提升计算效率。

  6. 便于动态扩容后重新计算哈希位置时能均匀分布元素。动态扩容仍然按照2的指数倍,所以按位与操作的值变化就是二进制高位+1,这种变化就会使得需要扩容的元素的哈希值重新按位与操作后得到的下标要么不变,要么加一倍,这样就能使得原本在同一链表上的元素均匀分布到新的哈希表中。(jdk1.8后才发现并使用)

    HashMap是否支持元素为null?

    支持

    哈希值相同,对象一定相同吗?对象相同,哈希值一定相同吗?

    不一定,一定。

    HashMap扩容的原因

    提升HashMap的get、put的效率,如果不扩容,链表就会越来越长,导致插入和查询的效率都会变低。

    引入红黑树后,如果单链表的节点个数超过8个,是否一定会树化?

    不一定。会先去判断是否需要扩容(即判断当前节点个数是否大于扩容的阈值),如果满足扩容条件,直接扩容,不会树化,因为扩容不仅能增加容量,还能缩短单链表的节点数,一举两得。

    扩容机制

    jdk1.8之前为头插法,jdk1.8改为尾插法,因为引入红黑树之后,就需要判断单链表的节点个数(超过8个转为红黑树),所以干脆使用尾插法,正好遍历单链表,读取节点个数,也正是尾插法,使得插入节点时,可以判断是否有重复的元素,从而去重。