juc面试题总结

Q1:什么是 CAS

CAS 表示 Compare And Swap,⽐较并交换,CAS 需要三个操作数,分别是内存位置 V、旧的预期值 A 和准备设置的新值 BCAS 指令执⾏时,当且仅当 V 符合 A 时,处理器才会⽤ B 更新 V 的值,否则它就不执⾏更新。但不管是否更新都会返回 V 的旧值,这些处理过程是原⼦操作,执⾏期间不会被其他线程打断。

 JDK 5 后,Java 类库中才开始使⽤ CAS 操作,该操作由 Unsafe 类⾥的 等⼏个⽅法包装提供。HotSpot 在内部对这些⽅法做了特殊处理,即时编译的结果是⼀条平台相关的处理器CAS 指令。Unsafe 类不是给⽤户程序调⽤的类,因此 JDK9 前只有 Java 类库可以使⽤ CAS,譬如 juc包⾥的 AtomicInteger类中 等⽅法都使⽤了Unsafe 类的 CAS 操作实现。

Q2CAS 有什么问题?

CAS 从语义上来说存在⼀个逻辑漏洞:如果 V 初次读取时是 A,并且在准备赋值时仍为 A,这依旧不能说明它没有被其他线程更改过,因为这段时间内假设它的值先改为 B ⼜改回 A,那么 CAS 操作就会误认为它从来没有被改变过。

这个漏洞称为 ABA 问题,juc 包提供了⼀个 AtomicStampedReference,原⼦更新带有版本号的引⽤类型,通过控制变量值的版本来解决 ABA 问题。⼤部分情况下 ABA 不会影响程序并发的正确性,如果需要解决,传统的互斥同步可能会⽐原⼦类更⾼效。

Q3:有哪些原⼦类?

JDK 5 提供了 java.util.concurrent.atomic 包,这个包中的原⼦操作类提供了⼀种⽤法简单、性能⾼效、线程安全地更新⼀个变量的⽅式。到 JDK  8  该包共有17个类,依据作⽤分为四种:原⼦更新基本类型类、原⼦更新数组类、原⼦更新引⽤类以及原⼦更新字段类,atomic 包⾥的类基本都是使⽤ Unsafe 实现的包装类。

AtomicInteger 原⼦更新整形、 AtomicLong 原⼦更新⻓整型、AtomicBoolean 原⼦更新布尔类型。

AtomicIntegerArray,原⼦更新整形数组⾥的元素、 AtomicLongArray 原⼦更新⻓整型数组⾥的元素、 AtomicReferenceArray 原⼦更新引⽤类型数组⾥的元素。

AtomicReference 原⼦更新引⽤类型、AtomicMarkableReference 原⼦更新带有标记位的引⽤类型, 可以绑定⼀个 boolean 标记、 AtomicStampedReference 原⼦更新带有版本号的引⽤类型,关联⼀个整数值作为版本号,解决 ABA 问题。

AtomicIntegerFieldUpdater 原⼦更新整形字段的更新器、 AtomicLongFieldUpdater 原⼦更新⻓整形字段的更新器AtomicReferenceFieldUpdater 原⼦更新引⽤类型字段的更新器。

Q4AtomicIntger 实现原⼦更新的原理是什么?

AtomicInteger 原⼦更新整形、 AtomicLong 原⼦更新⻓整型、AtomicBoolean 原⼦更新布尔类型。以原⼦⽅式将当前的值加 1

⾸先在 for 死循环中取得 AtomicInteger ⾥存储的数值,

第⼆步对 AtomicInteger 当前的值加 1

第三步调⽤⽅法进⾏原⼦更新,先检查当前数值是否等于 expect,如果等于则说明当前值没有被其他线程修改,则将值更新为 next,否则会更新失败返回 false,程序会进⼊ for 循环重新进⾏操作。

atomic 包中只提供了三种基本类型的原⼦更新,atomic 包⾥的类基本都是使⽤ Unsafe 实现的,

Unsafe 只提供三种 CAS ⽅法: compareAndSwapInt compareAndSwapLong

compareAndSwapObject ,例如原⼦更新 Boolean 是先转成整形再使⽤ compareAndSwapInt

Q5CountDownLatch 是什么?

CountDownLatch 是基于执⾏时间的同步类,允许⼀个或多个线程等待其他线程完成操作,构造⽅法接收⼀个 int 参数作为计数器,如果要等待 n 个点就传⼊ n。每次调⽤

1,   await ***阻塞当前线程直到计数器变为0,由于点既可以是 n 个线程也可以是⼀个线程⾥的 n 个执⾏步骤。

⽅法时计数器减⽅法可⽤在任何地⽅,所以 n

Q6CyclicBarrier 是什么?

循环屏障是基于同步到达某个点的信号量触发机制,作⽤是让⼀组线程到达⼀个屏障时被阻塞,直到最后⼀个线程到达屏障才会解除。构造⽅法中的参数表示拦截线程数量,每个线程调⽤ ⽅法告诉CyclicBarrier ⾃⼰已到达屏障,然后被阻塞。还⽀持在构造⽅法中传⼊⼀个 Runnable 任务,当线程到达屏障时会优先执⾏该任务。适⽤于多线程计算数据,最后合并计算结果的应⽤场景。

CountDownLacth 的计数器只能⽤⼀次,⽽ CyclicBarrier 的计数器可使⽤

⽅法重置,所以CyclicBarrier 能处理更为复杂的业务场景,例如计算错误时可⽤重置计数器重新计算。

Q7Semaphore 是什么?

信号量⽤来控制同时访问特定资源的线程数量,通过协调各个线程以保证合理使⽤公共资源。信号量可以⽤于流量控制,特别是公共资源有限的应⽤场景,⽐如数据库连接。

Semaphore 的构造⽅法参数接收⼀个 int 值,表示可⽤的许可数量即最⼤并发数。使⽤ ⽅法

获得⼀个许可证,使⽤⽅法归还许可,还可以⽤尝试获得许可。

Q8Exchanger 是什么?

交换者是⽤于线程间协作的⼯具类,⽤于进⾏线程间的数据交换。它提供⼀个同步点,在这个同步点两个线程可以交换彼此的数据。

两个线程通过⽅法交换数据,第⼀个线程执⾏⽅法后会阻塞等待第⼆个线程执⾏该⽅法,当两个线程都到达同步点时这两个线程就可以交换数据,将本线程⽣产出的数据传递给对⽅。应⽤场景包括遗传算法、校对⼯作等。

Q9JDK7 ConcurrentHashMap 原理?

ConcurrentHashMap ⽤于解决 HashMap 的线程不安全和 HashTable 的并发效率低,HashTable 所以效率低是因为所有线程都必须竞争同⼀把锁,假如容器⾥有多把锁,每⼀把锁⽤于锁容器的部分数       据,那么多线程访问容器不同数据段的数据时,线程间就不会存在锁竞争,从⽽有效提⾼并发效率,这       就是 ConcurrentHashMap 的锁分段技术。⾸先将数据分成 Segment 数据段,然后给每⼀个数据段配⼀把锁,当⼀个线程占⽤锁访问其中⼀个段的数据时,其他段的数据也能被其他线程访问。

get 实现简单⾼效,先经过⼀次再散列,再⽤这个散列值通过散列运算定位到 Segment,最后通过散列算法定位到元素。get 的⾼效在于不需要加锁,除⾮读到空值才会加锁重读。get ⽅法中将共享变量定义为 volatile,在 get 操作⾥只需要读所以不⽤加锁。

put 必须加锁,⾸先定位到 Segment,然后进⾏插⼊操作,第⼀步判断是否需要对 Segment ⾥的HashEntry 数组进⾏扩容,第⼆步定位添加元素的位置,然后将其放⼊数组。

size 操作⽤于统计元素的数量,必须统计每个 Segment 的⼤⼩然后求和,在统计结果累加的过程中, 之前累加过的 count 变化⼏率很⼩,因此先尝试两次通过不加锁的⽅式统计结果,如果统计过程中容器⼤⼩发⽣了变化,再加锁统计所有 Segment ⼤⼩。判断容器是否发⽣变化根据 modCount 确定。

Q10JDK8 ConcurrentHashMap 原理?

主要对 JDK7 做了三点改造:① 取消分段锁机制,进⼀步降低冲突概率。② 引⼊红⿊树结构,同⼀个哈希槽上的元素个数超过⼀定阈值后,单向链表改为红⿊树结构。③     使⽤了更加优化的⽅式统计集合内的元素数量。具体优化表现在:在 putresize size ⽅法中设计元素总数的更新和计算都避免了锁,使⽤ CAS 代替。

get 同样不需要同步,put 操作时如果没有出现哈希冲突,就使⽤ CAS 添加元素,否则使⽤

synchronized 加锁添加元素。

当某个槽内的元素个数达到 7 table 容量不⼩于 64 时,链表转为红⿊树。当某个槽内的元素减少到6 时,由红⿊树重新转为链表。在转化过程中,使⽤同步块锁住当前槽的⾸元素,防⽌其他线程对当前槽进⾏增删改操作,转化完成后利⽤ CAS 替换原有链表。由于 TreeNode 节点也存储了 next 引⽤,因此红⿊树转为链表很简单,只需从 first 元素开始遍历所有节点,并把节点从 TreeNode 转为 Node 型即可,当构造好新链表后同样⽤ CAS 替换红⿊树

Q11ArrayList 的线程安全集合是什么?

可以使⽤ CopyOnWriteArrayList 代替 ArrayList,它实现了读写分离。写操作复制⼀个新的集合,在新集合内添加或删除元素,修改完成后再将原集合的引⽤指向新集合。这样做的好处是可以⾼并发地进⾏     读写操作⽽不需要加锁,因为当前集合不会添加任何元素。使⽤时注意尽量设置容量初始值,并且可以     使⽤批量添加或删除,避免多次扩容,⽐如只增加⼀个元素却复制整个集合。

适合读多写少,单个添加时效率极低。CopyOnWriteArrayList fail-safe 的,并发包的集合都是这种机制,fail-safe 在安全的副本上遍历,集合修改与副本遍历没有任何关系,缺点是⽆法读取最新数据。这也是 CAP 理论中 C A 的⽭盾,即⼀致性与可⽤性的⽭盾。

最后呢,本文章的所有知识取自于B站高淇老师讲的Java300集教程,里面更加全面的讲述了关于Java面试中所能遇到的各种问题,包括解决问题的方法。小编也给大家准备了充分的资源:

Java300集icon-default.png?t=M7J4https://www.bilibili.com/video/BV1qL411u7eE

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值