AtomicInteger实现原理,结合java内存模型说一下volatile实现原理
volatile英[ˈvɒlətaɪl]美[ˈvɑːlətl] . 先学会读。单词原意是不稳定的
回答思路
- AtomicInteger是用来保证整形共享变量在多线程下的线程安全。底层实现是基于volatile和cas原理实现。volatile保证操作的可见性,cas自旋保证原子性。
- java内存模型中,每个线程会有自己的工作内存,工作内存中的变量从主内存中读取,修改完之后会同步到主内存。这种内存模型的设计在多线程环境下会有可见性和原子性问题。(可扩展)
- 可见性问题就是线程在修改了工作内存中的变量再同步到主内存这一过程会存在延时,这样其他线程并不能及时读取到这个修改的变量
- 原子性问题同样也是在这种同步不及时的模式下,会出现数据修改覆盖的情况
- volatile用于解决可见性问题。被volatile修饰的变量在修改完之后会立刻强制更新到主内存之中,同样的在读取时也是会从主内存读取
延伸问题
- volatile为什么只保证可见性,按照你的说法它应该能保证原子性吧?
- 什么是重排序、内存屏障?happens-before?内存屏障是一种防止重排序的手段,happens-before是一种规则
- 了解伪共享?
为什么volatile保证可见性却不保证原子性?
i = 0;
① int temp=i; ② i=i+1; ③ return i;
两个线程同时读取到i的值之后进行操作,并发到③赋值操作的时候,第一个线程把i=1 return 。第二个线程执行到③,此时它读到i=1,也只会return 1;
- java内存模型 - (JMM)java memory mode
- 物理内存模型带来的问题
- cpu缓存和内存
- 多处理器更新内容之后先放在缓存中,主内存还未更新时,会出现数据安全问题
- 缓存行伪共享 false sharing
- cpu缓存多级缓存在读写时都是以缓存行(一般64字节,32-256之间)为单位 ,
- 优点:连续空间的数据结构在读取时会更快,譬如数组。
- 缺点:如果存在多个处理器对不相关但在同一个缓存行的数据进行操作,(为了遵循缓存一致性协议 MESI)会出现频繁失效的情况,影响并发执行效率
- 解决方案:
- jdk1.6通过补位
- jdk1.7会对无意义的补位进行优化。除了补位之外还要加上继承
- jdk1.8中提供注解@sun.misc.Contended 并且需要在启动时设置参数。(ConcurrentHashMap,Thread)
- cpu缓存和内存
- jmm导致的并发问题
- 模型:每个线程都有自己的工作内存,线程对变量的所有操作都必须在工作内存中,不能直接对主内存进行操作。且每个线程不能访问其他线程的工作内存。
- 可见性
- 读到的不是最新的 - 工作线程修改了内容更新到主内存会有一段时间,另一个工作线程并不能及时读取到
- 解决 volatile & 锁
- 一致性问题
- 同时修改的时候会丢失操作
- 锁 (并行-串行)
- 重排序 - 为了优化程序性能会对代码做一些重排序(减少寄存器反复读取,多处理器并行操作)
- 编译器 - 指令级 - 内存
- 虽然重排序可以优化代码执行,但是也不能随便重排代码。遵循原则
- 操作不具备数据依赖关系
- 单线程程序执行结果不能改变 as-if-serial语义
- 重排序在多线程下会有问题 https://www.jianshu.com/p/9b3845714d8a
- 内存屏障 防止重排序 load & store https://www.jianshu.com/p/08a0a8c984ab
- store屏障 保证写操作写回主存
- load屏障 保证读到主存的值
- 内存屏障 防止重排序 load & store https://www.jianshu.com/p/08a0a8c984ab
- 临界区重排序问题导致单例模式 双重检查异常
- happens - before 原则 多线程重排序之后结果不会改变
-
class ReorderExample { int a = 0; boolean flag = false; public void writer() { a = 1; //1 flag = true; //2 } Public void reader() { if (flag) { //3 int i = a * a; //4 …… } } } // ①2 3 4 1 -> i = 0 ② 4 1 2 3 -> i = 0
- volatile 内存语义
- 修改完变量值之后会把本地内存的值刷新到主存
- 对volatile写操作时,会在前面加入一个storestore屏障,且在后面加入一个storeload屏障。别人写完我才能写,我写完别人才能读写
- 对volatile读操作时,会在后面加入一个loadload和loadstore屏障。
- 底层原理(cpu级别的锁) 在执行写操作时,使用cpu的lock前缀指令(lock:xx)
- 会将当前cpu缓存行的数据写到主内存里面
- 让其他cpu缓存行的数据失效
- 物理内存模型带来的问题
另外看个小甜点 i++和i=i++ 问题 https://mp.weixin.qq.com/s/wuuGNvrOTIPgD9GYmECuJg
代码
int a = 0;
System.out.println(a++);
System.out.println(a);
下面是编译结果
int a = 0;
byte var10001 = a;
int a = a + 1;
System.out.println(var10001);
System.out.println(a);
////////////////////////////////////////
代码
int a = 0; a = a++; System.out.println(a); 下面是编译代码 int a = 0; byte var10000 = a; int var2 = a + 1; a = var10000; System.out.println(a);
1、i++有三项操作,将值赋给中间变量int temp=i;i=i+1;return i;
2、i=i++有四项操作,将值赋给中间变量int temp=i;i=i+1;i=temp;return i;
是谁来自江河湖海,却囿于昼夜厨房与爱