总结
我们总是喜欢瞻仰大厂的大神们,但实际上大神也不过凡人,与菜鸟程序员相比,也就多花了几分心思,如果你再不努力,差距也只会越来越大。
面试题多多少少对于你接下来所要做的事肯定有点帮助,但我更希望你能透过面试题去总结自己的不足,以提高自己核心技术竞争力。每一次面试经历都是对你技术的扫盲,面试后的复盘总结效果是极好的!
每个线程在执行的时候首先会将数据从主内存中加载至私有内存然后再进行操作。
Volatile修饰
知道JMM模型的主要内容之后,让我们想象一个Java使用场景:
- 首先线程A创建对象
Integer a = 10;
此时JVM会为a
在堆中分配一块内存,此时线程A运行结束。 - 线程B,线程C被创建,这两者都要对
a
进行修改,所以JVM会将a
复制到B和C的栈中,BC两者分别都对a进行a = a + 1
操作,在B中此时a=2,操作结束之后a=3,在C中此时a=2,操作结束之后a=3,操作结束之后这两个线程会将a
写回堆内存中。此时问题就发生了,a
虽然进行了两次+1操作但是结果仍然是3。
为了解决这个问题我们可以对a
进行volatile
修饰,volatile
关键字会使被修饰的变量有两个特点:
- 变量在内存中的可见性
(1) 当有线程对volatile变量进行修改的时候JVM会强制将该变量在主内存中数据的进行刷新。
(2) 这种刷新会让别的线程已被加载的该变量无效化。 - 禁止指令重排序
这样子似乎就能解决并发问题了,可是volatile
虽然能保证变量在内存中的可见性,但是却无法保证原子性。
让我们再想象一个使用场景:
- 首先我们有
volatile Integer a = 10;
。 - 线程A要进行以下操作
a++;
。 - 线程B要进行以下操作
a = 0;
。 - A与B并发执行,由于
a++
这个操作并不是原子操作,在实际执行的时候会先获得a值再进行自加,所以实际操作的过程中获得a值之后可能会被中断。理想的结果是A中的a最后等于11,但是假如A执行一半被中断了,转换为B去执行,B执行完之后A继续,那么此时结果就变成了1。
我们可以发现,volatile
虽然很好,但是还不够好。为了解决上面的问题,我们可以对A线程加锁,使用sychronized对该代码段进行修饰,但是sychronized是一种阻塞式的独占锁悲观锁,在这里介绍一种非阻塞的乐观锁,i.e. 自旋锁,所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。在自旋锁内部里有一个非常重要的概念:CAS方法。
CAS(Compare And Swap)
- 首先CAS是一种思想。
- 其次CAS是一种CPU的原子指令。
- 最后在Java的
concurrent
包中有一系列基于CAS的native方法,如compareAndSet
。
那么什么是CAS?
顾名思义,CAS(Compare And Swap)先要比较内存中实际的数值与预期数值是否相等,若相等则将内存中的实际值设置为新值。
来看代码示例:
使用Java的concurrent
包中有原子类如AtomicInteger
,其实现了CAS的操作方法。
AtomicInteger a = new AtomicInteger(10);
System.out.println(a.compareAndSet(10, 20) + " " + a);
System.out.println(a.compareAndSet(10, 30) + " " + a);
// 输出结果:
// true 20
// false 20
首先a
为10,第一步compareAndSet
预期值为10,实际值为10,可以成功将a
设置为新值20。
第二步compareAndSet
预期值为10,实际值为20,设置新值失败。
通过CAS操作,我们就可以避免上面提到的并发问题。
下面是一个基于CAS方法的乐观锁自增实现,摘自Java乐观锁的实现原理和典型案例:
public class Counter {
private AtomicInteger value = new AtomicInteger(0);//实际值
public void increment() {
int expect;
int update;
do {
expect = value.get(); // 预期值
update = expect + 1; // 新值
// 比较value与expect是否相等,相等则使用update更新value
} while (!value.compareAndSet(expect, update));
}
}
在上述代码中如果在do
代码块中被中断并且value
被修改,则value
不会被更新,继续循环,直到do
代码块不被中断,则value
可以被更新,循环结束。
不难看出,这段代码实际上就是一个乐观锁的实现,并且具有以下特点。
- 不会阻塞线程。i.e. 非阻塞
- 总是假设
value
没有被更新,若检测到value
被更新了(线程冲突),则更新失败。i.e. 乐观
Kafka进阶篇知识点
Kafka高级篇知识点
44个Kafka知识点(基础+进阶+高级)解析如下
由于篇幅有限,小编已将上面介绍的**《Kafka源码解析与实战》、Kafka面试专题解析、复习学习必备44个Kafka知识点(基础+进阶+高级)都整理成册,全部都是PDF文档**
心总结学习笔记+最新讲解视频+实战项目源码】](https://bbs.csdn.net/topics/618154847)收录**