JAVA基础集合相关

  • hashmap如何解决hash冲突,为什么hashmap中的链表需要转成红黑树

1、hash方法把hashCode再散列一次,把散列hashCode后的值作为返回值返回,以此再次减少冲突,而过程是把高位的特征性传到低位

2、若干对象变成链表挂在一个数组位置

红黑树改进了链表过长查询遍历慢问题和resize时出现导致put死循环的bug

1、红黑树的平均查找长度是log(n),长度为8,查找长度为log(8)=3,链表的平均查找长度为n/2,当长度为8时,平均查找长度为8/2=4,这才有转换成树的必要;链表长度如果是小于等于6,6/2=3,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短

2、中间有个差值7可以防止链表和树之间频繁的转换。假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低

  • hashmap什么时候会触发扩容

如果当前HashMap的容量超过threshold则进行扩容

  • jdk1.8之前并发操作hashmap时为什么会有死循环的问题?

进行put操作到阈值时,进行扩容的时候可能会出现死循环。将旧数组的数据移到新数组对应位置时,该位置上如果有旧数组上的数据,就会出现死循环的问题

  • hashmap扩容时每个entry需要再计算一次hash吗?

JDK1.8不需要,JDK需要重新计算hash,JDK1.8通过使用e.hash & oldCap来计算高位和低位的hash值,来把原来在一个槽位上面的链表拆分成两个链表即可

  • hashmap的数组长度为什么要保证是2的幂?
  1. 能利用 & 操作代替 % 操作,提升性能
  2. 数组扩容时,仅仅关注 “特殊位” 就可以重新定位元素
  • 如何用LinkedHashMap实现LRU?

LRU(Least Recently Used):

最近最久未使用策略,优先淘汰最久未使用的数据,也就是上次被访问时间距离现在最久的数据。该策略可以保证内存中的数据都是热点数据,也就是经常被访问的数据,从而保证缓存命中率。

使用LinkedHashMap实现

LinkedHashMap底层就是用的【**HashMap**】加【**双链表**】实现的,而且本身已经实现了按照访问顺序的存储。此外,LinkedHashMap中本身就实现了一个方法removeEldestEntry用于判断是否需要移除最不常读取的数,方法默认是直接返回false,不会移除元素,所以需要【重写该方法】,即当缓存满后就移除最不常用的数。

  • 如何用TreeMap实现一致性hash?

 

  • ConcurrentHashMap是如何在保证并发安全的同时提高性能?

ConcurrentHashMap会把数据分段存储,然后每段数据分别加锁,jdk1.7中采用了继承了重入锁ReentrantLock的segment来实现分段加锁;jdk1.8中则使用CAS乐观锁和volatile代替RentrantLock,spread二次哈希进行segment分段,stream提高并行处理能力

  • ConcurrentHashMap是如何让多线程同时参与扩容?

在多线的环境下,用volatile的方式读取sizectrl属性的值,来判断map所处的状态,通过cas修改操作来告诉其它线程Map的状态类型。不同的数值类型,代表着不同的状态

  • LinkedBlockingQueue、DelayQueue是如何实现的?

ArrayBlockingQueue:ArrayBlockingQueue的并发阻塞是通过ReentrantLock和Condition来实现的,ArrayBlockingQueue内部只有一把锁,意味着同一时刻只有一个线程能进行入队或者出队的操作

数组阻塞队列:一个lock,2个condition,放的时候放和取都不行,取的时候取和放都不行。为空了取的线程都在emptyCondition等待,满了放的线程都在fullCondition上面等待,唤醒时候只唤醒一个线程。只能同时一个取或者放。

LinkedBlockingQueue:是一个基于链表实现的可选容量的阻塞队列。队头的元素是插入时间最长的,队尾的元素是最新插入的。新的元素将会被插入到队列的尾部。 LinkedBlockingQueue的容量限制是可选的,如果在初始化时没有指定容量,那么默认使用int的最大值作为队列容量

LinkedBlockingQueue中维持两把锁,一把锁用于入队,一把锁用于出队,这也就意味着,同一时刻,只能有一个线程执行入队,其余执行入队的线程将会被阻塞;同时,可以有另一个线程执行出队,其余执行出队的线程将会被阻塞。换句话说,虽然入队和出队两个操作同时均只能有一个线程操作,但是可以一个入队线程和一个出队线程共同执行,也就意味着可能同时有两个线程在操作队列,那么为了维持线程安全,LinkedBlockingQueue使用一个AtomicInterger类型的变量表示当前队列中含有的元素个数,所以可以确保两个线程之间操作底层队列是线程安全的

链表阻塞队列:2个锁,2个confition,放的时候不能放可以取(也只是一个取),取的时候不能取可以放(只能一个放)。只能同时一个放一个取。都是通过count来平衡空和满的

DelayQueue:DelayQueue是一个没有边界BlockingQueue实现,加入其中的元素必需实现Delayed接口。当生产者线程调用put之类的方法加入元素时,会触发Delayed接口中compareTo方法进行排序,也就是说队列中元素的顺序是按到期时间排序的,而非它们进入队列的顺序。排在队列头部的元素是最早到期的,越往后到期时间赿晚。消费者线程查看队列头部的元素,注意是查看不是取出。然后调用元素的getDelay方法,如果此方法返回的值小0或者等于0,则消费者线程会从队列中取出此元素,并进行处理。如果getDelay方法返回的值大于0,则消费者线程wait返回的时间值后,再从队列头部取出元素,此时元素应该已经到期。

DelayQueue是Leader-Followr模式的变种,消费者线程处于等待状态时,总是等待最先到期的元素,而不是长时间的等待。消费者线程尽量把时间花在处理任务上,最小化空等的时间,以提高线程的利用效率

DelayQueue内部实现的机制:以支持优先级无界队列的PriorityQueue作为一个容器,容器里面的元素都应该实现Delayed接口,在每次往优先级队列中添加元素时以元素的过期时间作为排序条件,最先过期的元素放在优先级最高

  • CopyOnWriteArrayList是如何保证线程安全的?

CopyOnWriteArrayList使用了一种叫写时复制的方法,当有新元素添加到CopyOnWriteArrayList时,先从原有的数组中拷贝一份出来,然后在新的数组做写操作,写完之后,再将原来的数组引用指向到新数组。创建新数组,并往新数组中加入一个新元素,这个时候,array这个引用仍然是指向原数组的。当元素在新数组添加成功后,将array这个引用指向新数组。

CopyOnWriteArrayList的整个add操作都是在锁的保护下进行的。这样做是为了避免在多线程并发add的时候,复制出多个副本出来,把数据搞乱了,导致最终的数组数据不是我们期望的

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值