吃透这JAVA并发十二核心,面试官都得对你刮目相看

推荐阅读:

话不多说,干货走起。

1、HashMap

面试第一题必问的 HashMap,挺考验Javaer的基础功底的,别问为啥放在这,因为重要!HashMap具有如下特性

  1. HashMap 的存取是没有顺序的。
  2. KV 均允许为 NULL。
  3. 多线程情况下该类不安全,可以考虑用 HashTable。
  4. JDk8底层是数组 + 链表 + 红黑树,JDK7底层是数组 + 链表。
  5. 初始容量和装载因子是决定整个类性能的关键点,轻易不要动。
  6. HashMap是懒汉式创建的,只有在你put数据时候才会 build。
  7. 单向链表转换为红黑树的时候会先变化为双向链表最终转换为红黑树,切记双向链表跟红黑树是共存的。
  8. 对于传入的两个key,会强制性的判别出个高低,目的是为了决定向左还是向右放置数据。
  9. 链表转红黑树后会努力将红黑树的root节点和链表的头节点 跟table[i]节点融合成一个。
  10. 在删除的时候是先判断删除节点红黑树个数是否需要转链表,不转链表就跟RBT类似,找个合适的节点来填充已删除的节点。
  11. 红黑树的root节点不一定table[i]也就是链表的头节点是同一个,三者同步是靠MoveRootToFront实现的。而HashIterator.remove()会在调用removeNode的时候movable=false

常见HashMap考点:

  1. HashMap原理,内部数据结构。
  2. HashMap中的put、get、remove大致过程。
  3. HashMap中 hash函数实现。
  4. HashMap如何扩容。
  5. HashMap几个重要参数为什么这样设定。
  6. HashMap为什么线程不安全,如何替换。
  7. HashMap在JDK7跟JDK8中的区别。
  8. HashMap中链表跟红黑树切换思路。
  9. JDK7中 HashMap环产生原理。

2、ConcurrentHashMap

ConcurrentHashMap是多线程模式下常用的并发容器,它的实现在JDK7JDK8区别挺大的。

2.1 JDK7

JDK7中的 ConcurrentHashMap 使用 Segment + HashEntry 分段锁实现并发,它的缺点是并发程度是由Segment 数组个数来决定的,并发度一旦初始化无法扩容,扩容的话只是HashEntry的扩容。

Segment 继承自 ReentrantLock,在此扮演锁的角色。可以理解为我们的每个Segment都是实现了Lock功能的HashMap。如果我们同时有多个Segment形成了Segment数组那我们就可以实现并发咯。

大致的put流程如下:

  1. ConcurrentHashMap底层大致实现?

ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的HashTable,只要多个修改操作发生在不同的段上就可以并发进行。

  1. ConcurrentHashMap在并发下的情况下如何保证取得的元素是最新的?

用于存储键值对数据的HashEntry,在设计上它的成员变量valuenext都是volatile类型的,这样就保证别的线程对value值的修改,get方法可以马上看到,并且get的时候是不用加锁的

  1. ConcurrentHashMap弱一致性体现在clear和get方法,原因在于没有加锁

比如迭代器在遍历数据的时候是一个Segment一个Segment去遍历的,如果在遍历完一个Segment时正好有一个线程在刚遍历完的Segment上插入数据,就会体现出不一致性。clear也是一样。get方法和containsKey方法都是遍历对应索引位上所有节点,都是不加锁来判断的,如果是修改性质的因为可见性的存在可以直接获得最新值,不过如果是新添加值则无法保持一致性

  1. size 统计个数不准确

size方法比较有趣,先无锁的统计所有的数据量看下前后两次是否数据一样,如果一样则返回数据,如果不一样则要把全部的segment进行加锁,统计,解锁。并且size方法只是返回一个统计性的数字。

2.2 JDK8

ConcurrentHashMap 在JDK8中抛弃了分段锁,转为用 CAS + synchronized,同时将HashEntry改为Node,还加入了红黑树的实现,主要还是看put的流程(如果看了扩容这块,绝对可以好好吹逼一番)。

ConcurrentHashMap 是如果来做到高效并发安全

  1. 读操作

get方法中根本没有使用同步机制,也没有使用unsafe方法,所以读操作是支持并发操作的。

  1. 写操作

基本思路跟HashMap的写操作类似,只不过用到了CAS + syn 实现加锁,同时还涉及到扩容的操作。JDK8中锁已经细化到 table[i] 了,数组位置不同可并发,位置相同则去帮忙扩容。

  1. 同步处理主要是通过synunsafe的硬件级别原子性这两种方式完成
  1. 当我们对某个table[i]操作时候是用syn加锁的。
  2. 取数据的时候用的是unsafe硬件级别指令,直接获取指定内存的最新数据。

3 、并发基础知识

并发编程的出发点:充分利用CPU计算资源,多线程并不是一定比单线程快,要不为什么 Redis 6.0版本的核心操作指令仍然是单线程呢?对吧!

多线程跟单线程的性能都要具体任务具体分析,talk is cheap, show me the picture

3.1 进程跟线程

进程

进程是操作系统调用的最小单位,是系统进行资源分配和调度的独立单位。

线程

  1. 因为进程的创建、销毁、切换产生大量的时间和空间的开销,进程的数量不能太多,而线程是比进程更小的能独立运行的基本单位,他是进程的一个实体,是CPU调度的最小单位。线程可以减少程序并发执行时的时间和空间开销,使得操作系统具有更好的并发性。
  2. 线程基本不拥有系统资源,只有一些运行时必不可少的资源,比如程序计数器、寄存器和栈,进程则占有堆、栈。线程,Java默认有两个线程 main 跟GC。Java是没有权限开线程的,无法操作硬件,都是调用的 nativestart0 方法 由 C++ 实现

3.2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值