- Day2,第三篇
- 本篇文章的主题是
【Java实习生面试题系列】-- 多线程篇三
。
1. 说下对 ReentrantReadWriteLock 的理解?
首先 ReentrantLock
某些时候有局限,如果使用 ReentrantLock
,可能本身是为了防止线程A在写数据、线程B在读数据造成的数据不一致,但这样,如果线程C在读数据、线程D也在读数据,读数据是不会改变数据的,没有必要加锁,但是还是加锁了,降低了程序的性能。
因为这个,才诞生了读写锁 ReadWriteLock
。 ReadWriteLock
是一个读写锁接口,ReentrantReadWriteLock
是 ReadWriteLock
接口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能
2. 说下对悲观锁和乐观锁的理解?
- 乐观锁:乐观锁是相对悲观锁而言的,
乐观锁假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测
,如果冲突,则返回给用户异常信息,让用户决定如何去做。乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量。 - 悲观锁:之所以叫做悲观锁,是因为这是一种对数据的修改持有悲观态度的并发控制方式。
总是假设最坏的情况,每次读取数据的时候都默认其他线程会更改数据,因此需要进行加锁操作
,当其他线程想要访问数据时,都需要阻塞挂起。
3. 乐观锁常见的两种实现方式是什么?
- 版本号机制:就是会有一个
version
字段,就是每一次修改对应数据行的时候,我们都会对对应的版本号字段进行比较,如果和预期的相同就进行更新,否则更新失败。 - CAS算法:全称
Compare and swap
,即比较并交换,它是一条 CPU 同步原语。是一种硬件对并发的支持,针对多处理器操作而设计的一种特殊指令,用于管理对共享数据的并发访问。
- CAS 是一种无锁的非阻塞算法的实现。
CAS 包含了 3 个操作数:
-
- 需要读写的内存值
V
- 旧的预期值
A
- 要修改的更新值
B
- 需要读写的内存值
- 当且仅当
V
的值等于A
时,CAS 通过原子方式用新值B
来更新V
的 值,否则不会执行任何操作(他的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的
。)
CAS 并发原语体现在 Java 语言中的 sum.misc.Unsafe
类中的各个方法。调用 Unsafe 类中的 CAS 方法, JVM 会帮助我们实现出 CAS 汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于 CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,CAS 是一条 CPU 的原子指令,不会造成数据不一致问题。
4. CAS有什么缺陷?
1. ABA 问题
并发环境下,假设初始条件是 A
,去修改数据时,发现是 A
就会执行修改。但是看到的虽然是 A
,中间可能发生了 A
变 B
, B
又变回 A
的情况。此时 A
已经非彼 A
,数据即使成功修改,也可能有问题。
可以通过 AtomicStampedReference
解决ABA问题,它,一个带有标记的原子引用类,通过控制变量值的版本来保证CAS的正确性。
2. 循环时间长开销
自旋CAS,如果一直循环执行,一直不成功,会给CPU带来非常大的执行开销。
很多时候,CAS思想体现,是有个自旋次数的
,就是为了避开这个耗时问题~
3. 只能保证一个变量的原子操作。
CAS 保证的是对一个变量执行操作的原子性,如果对多个变量操作时,CAS 目前无法直接保证操作的原子性的。
可以通过这两个方式解决这个问题:
- 使用互斥锁来保证原子性;
- 将多个变量封装成对象,通过
AtomicReference
来保证原子性。
5. CAS 和 synchronized 的使用场景?
- 对于资源竞争较少的情况使用
synchronized
。 - 对于资源竞争严重的情况,
CAS
自旋的概率比较大。
6. 简单说下对 Java 中的原子类的理解?
Atomic
是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
所以,所谓原子类说简单点就是 具有原子 / 原子操作特征
的类。
并发包 java.util.concurrent
的原子类都存放在 java.util.concurrent.atomic
下:
7. AtomicInteger 的原理是什么?
AtomicInteger
类主要利用 CAS
和 volatile
和 native
方法来保证原子操作,从而避免 synchronized
的高开销,执行效率大为提升。
AtomicInteger
类的部分源码:
// 更新操作时提供“比较并替换”的作用
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try{
valueOffset = unsafe.objectFieldOffset(AutomicInteger.class.getDeclaredField("value"));
}catch(Exception ex){
throw new Error(ex);
}
}
private volatile int value;
今天的面试题就总结这么一些吧,总结面试题也花费了我不少时间,所以说总结不易,如果你感觉对你有帮助的话,请你三连支持,后面的文章会一点点更新。