预览
并发
问题 | 详解 |
---|---|
请谈谈你对volatile的理解 | link |
CAS你知道吗? | link |
原子类Atomiclnteger的ABA问题谈谈?原子更新引用知道吗? | link |
我们知道ArrayList是线程不安全,请编码写一个不安全的案例并给出解决方案 | link |
公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自旋锁 | link |
CountDownLatch/CyclicBarrier/Semaphore使用过吗? | link |
阻塞队列知道吗? | link |
线程池用过吗?ThreadPoolExecutor谈谈你的理解? | link |
线程池用过吗?生产上你如何设置合理参数 | link |
死锁编码及定位分析 | link |
JVM
问题 | 详解 |
---|---|
JVM垃圾回收的时候如何确定垃圾?是否知道什么是GC Roots | link |
你说你做过JVM调优和参数配置,请问如何盘点查看JVM系统默认值 | link |
你平时工作用过的JVM常用基本配置参数有哪些? | link |
强引用、软引用、弱引用、虚引用分别是什么?请谈谈你对OOM的认识 | link |
GC垃圾回收算法和垃圾收集器的关系?分别是什么请你谈谈怎么查看服务器默认的垃圾收集器是那个? | link |
生产上如何配置垃圾收集器的? | link |
谈谈你对垃圾收集器的理解?G1垃圾收集器 | link |
生产环境服务器变慢,诊断思路和性能评估谈谈? | link |
假如生产环境出现CPU占用过高,请谈谈你的分析思路和定位 | link |
对于JDK自带的JVM监控和性能分析工具用过哪些?一般你是怎么用的? | link |
- | - | - |
---|---|---|
预览 | 01_本课程前提要求和说明 | 02_volatile是什么 |
03_JMM内存模型之可见性 | 04_可见性的代码验证说明 | 05_volatile不保证原子性 |
06_volatile不保证原子性理论解释 | 07_volatile不保证原子性问题解决 | 08_volatile指令重排案例1 |
09_volatile指令重排案例2 | 10_单例模式在多线程环境下可能存在安全问题 | 11_单例模式volatile分析 |
12_CAS是什么 | 13_CAS底层原理-上 | 14_CAS底层原理-下 |
15_CAS缺点 | 16_ABA问题 | 17_AtomicReference原子引用 |
18_AtomicStampedReference版本号原子引用 | 19_ABA问题的解决 | 20_集合类不安全之并发修改异常 |
21_集合类不安全之写时复制 | 22_集合类不安全之Set | 23_集合类不安全之Map |
24_TransferValue醒脑小练习 | 25_java锁之公平和非公平锁 | 26_java锁之可重入锁和递归锁理论知识 |
27_java锁之可重入锁和递归锁代码验证 | 28_java锁之自旋锁理论知识 | 29_java锁之自旋锁代码验证 |
30_java锁之读写锁理论知识 | 31_java锁之读写锁代码验证 | 32_CountDownLatch |
33_CyclicBarrierDemo | 34_SemaphoreDemo | 35_阻塞队列理论 |
36_阻塞队列接口结构和实现类 | 37_阻塞队列api之抛出异常组 | 38_阻塞队列api之返回布尔值组 |
39_阻塞队列api之阻塞和超时控制 | 40_阻塞队列之同步SynchronousQueue队列 | 41_线程通信之生产者消费者传统版 |
42_Synchronized和Lock有什么区别 | 43_锁绑定多个条件Condition | 44_线程通信之生产者消费者阻塞队列版 |
45_Callable接口 | 46_线程池使用及优势 | 47_线程池3个常用方式 |
48_线程池7大参数入门简介 | 49_线程池7大参数深入介绍 | 50_线程池底层工作原理 |
51_线程池的4种拒绝策略理论简介 | 52_线程池实际中使用哪一个 | 53_线程池的手写改造和拒绝策略 |
54_线程池配置合理线程数 | 55_死锁编码及定位分析 | 56_JVMGC下半场技术加强说明和前提知识要求 |
57_JVMGC快速回顾复习串讲 | 58_谈谈你对GCRoots的理解 | 59_JVM的标配参数和X参数 |
60_JVM的XX参数之布尔类型 | 61_JVM的XX参数之设值类型 | 62_JVM的XX参数之XmsXmx坑题 |
63_JVM盘点家底查看初始默认值 | 64_JVM盘点家底查看修改变更值 | 65_堆内存初始大小快速复习 |
66_常用基础参数栈内存Xss讲解 | 67_常用基础参数元空间MetaspaceSize讲解 | 68_常用基础参数PrintGCDetails回收前后对比讲解 |
69_常用基础参数SurvivorRatio讲解 | 70_常用基础参数NewRatio讲解 | 71_常用基础参数MaxTenuringThreshold讲解 |
72_强引用Reference | 73_软引用SoftReference | 74_弱引用WeakReference |
75_软引用和弱引用的适用场景 | 76_WeakHashMap案例演示和解析 | 77_虚引用简介 |
78_ReferenceQueue引用队列介 | 79_虚引用PhantomReference | 80_GCRoots和四大引用小总结 |
81_SOFE之StackOverflowError | 82_OOM之Java heap space | 83_OOM之GC overhead limit exceeded |
84_OOM之Direct buffer memory | 85_OOM之unable to create new native thread故障演示 | 86_OOM之unable to create new native thread上限调整 |
87_OOM之Metaspace | 88_垃圾收集器回收种类 | 89_串行并行并发G1四大垃圾回收方式 |
90_如何查看默认的垃圾收集器 | 91_JVM默认的垃圾收集器有哪些 | 92_GC之7大垃圾收集器概述 |
93_GC之约定参数说明 | 94_GC之Serial收集器 | 95_GC之ParNew收集器 |
96_GC之Parallel收集器 | 97_GC之ParallelOld收集器 | 98_GC之CMS收集器 |
99_GC之SerialOld收集器 | 100_GC之如何选择垃圾收集器 | 101_GC之G1收集器 |
102_GC之G1底层原理 | 103_GC之G1参数配置及和CMS的比较 | 104_JVMGC结合SpringBoot微服务优化简介 |
105_Linux命令之top | 106_Linux之cpu查看vmstat | 107_Linux之cpu查看pidstat |
108_Linux之内存查看free和pidstat | 109_Linux之硬盘查看df | 110_Linux之磁盘IO查看iostat和pidstat |
111_Linux之网络IO查看ifstat | 112_CPU占用过高的定位分析思路 | 113_GitHub骚操作之开启 |
114_GitHub骚操作之常用词 | 115_GitHub骚操作之in限制搜索 | 116_GitHub骚操作之star和fork范围搜索 |
117_GitHub骚操作之awesome搜索 | 118_GitHub骚操作之#L数字 | 119_GitHub骚操作之T搜索 |
120_GitHub骚操作之搜索区域活跃用户 | - | - |
01_本课程前提要求和说明
教学视频
一些大厂的面试题
蚂蚁花呗一面:
- Java容器有哪些?哪些是同步容器,哪些是并发容器?
- ArrayList和LinkedList的插入和访问的时间复杂度?
- java反射原理,注解原理?
- 新生代分为几个区?使用什么算法进行垃圾回收?为什么使用这个算法?
- HashMap在什么情况下会扩容,或者有哪些操作会导致扩容?
- HashMap push方法的执行过程?
- HashMap检测到hash冲突后,将元素插入在链表的末尾还是开头?
- 1.8还采用了红黑树,讲讲红黑树的特性,为什么人家一定要用红黑树而不是AVL、B树之类的?
- https和http区别,有没有用过其他安全传输手段?
- 线程池的工作原理,几个重要参数,然后给了具体几个参数分析线程池会怎么做,最后问阻塞队列的作用是什么?
- linux怎么查看系统负载情况?
- 请详细描述springmvc处理请求全流程?spring 一个bean装配的过程?
- 讲一讲AtomicInteger,为什么要用CAS而不是synchronized?
美团一面:
- 最近做的比较熟悉的项目是哪个,画一下项目技术架构图。
- JVM老年代和新生代的比例?
- YGC和FGC发生的具体场景?
- jstack,jmap,jutil分别的意义?如何线上排查JVM的相关问题?
- 线程池的构造类的方法的5个参数的具体意义?
- 单机上一个线程池正在处理服务如果忽然断电怎么办(正在处理和阻塞队列里的请求怎么处理)?
- 使用无界阻塞队列会出现什么问题?接口如何处理重复请求?
百度一面:
- 介绍一下集合框架?
- hashmap hastable 底层实现什么区别?hashtable和concurrenthashtable呢?
- hashmap和treemap什么区别?低层数据结构是什么?
- 线程池用过吗都有什么参数?底层如何实现的?
- sychnized和Lock什么区别?sychnize 什么情况情况是对象锁?什么时候是全局锁为什么?
- ThreadLocal 是什么底层如何实现?写一个例子呗?
- volitile的工作原理?
- cas知道吗如何实现的?
- 请用至少四种写法写一个单例模式?
- 请介绍一下JVM内存模型?用过什么垃圾回收器都说说呗线上发送频繁full gc如何处理?CPU使用率过高怎么办?如何定位问题?如何解决说一下解决思路和处理方法
- 知道字节码吗?字节码都有哪些?Integer x =5,int y =5,比较x =y 都经过哪些步骤?讲讲类加载机制呗都有哪些类加载器,这些类加载器都加载哪些文件?
- 手写一下类加载Demo
- 知道osgi吗?他是如何实现的?
- 请问你做过哪些JVM优化?使用什么方法达到什么效果?
- classforName(“java.lang.String”)和String classgetClassLoader() LoadClass(“java.lang.String”)什么区别啊?
今日头条
- HashMap如果一直put元素会怎么样? hashcode全都相同如何?
- ApplicationContext的初始化过程?
- GC 用什么收集器?收集的过程如何?哪些部分可以作为GC Root?
- Volatile关键字,指令重排序有什么意义 ?synchronied,怎么用?
- Redis数据结构有哪些?如何实现sorted set?
- 并发包里的原子类有哪些,怎么实现?
- MvSql索引是什么数据结构? B tree有什么特点?优点是什么?
- 慢查询怎么优化?
- 项目: cache,各部分职责,有哪些优化点
京东金融面试
- Dubbo超时重试;Dubbo超时时间设置
- 如何保障请求执行顺序
- 分布式事务与分布式锁(扣款不要出现负数)
- 分布式Session设置
- 执行某操作,前50次成功,第51次失败a全部回滚b前50次提交第51次抛异常,ab场景分别如何设计Spring (传播特性)
- Zookeeper有却些作用
- JVM内存模型
- 数据库垂直和水平拆分
- MyBatis如何分页;如何设置缓存;MySQL分页
蚂蚁金服二面
- 自我介绍、工作经历、技术栈
- 项目中你学到了什么技术?(把三项目具体描述了很久)
- 微服务划分的粒度
- 微服务的高可用怎么保证的?
- 常用的负载均衡,该怎么用,你能说下吗?
- 网关能够为后端服务带来哪些好处?
- Spring Bean的生命周期
- HashSet是不是线程安全的?为什么不是线程安全的?
- Java 中有哪些线程安全的Map?
- Concurrenthashmap 是怎么做到线程安全的?
- HashTable你了解过吗?
- 如何保证线程安全问题?
- synchronized、lock
- volatile 的原子性问题?为什么i++这种不支持原子性﹖从计算机原理的设计来讲下不能保证原子性的原因
- happens before 原理
- cas操作
- lock和 synchronized 的区别?
- 公平锁和非公平锁
- Java读写锁
- 读写锁设计主要解决什么问题?
02_volatile是什么
volatile是JVM提供的轻量级的同步机制
- 保证可见性
- 不保证原子性
- 禁止指令重排(保证有序性)
03_JMM内存模型之可见性
JMM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。
JMM关于同步的规定:
- 线程解锁前,必须把共享变量的值刷新回主内存
- 线程加锁前,必须读取主内存的最新值到自己的工作内存
- 加锁解锁是同一把锁
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成,其简要访问过程如下图:
可见性
通过前面对JMM的介绍,我们知道各个线程对主内存中共享变量的操作都是各个线程各自拷贝到自己的工作内存进行操作后再写回到主内存中的。
这就可能存在一个线程AAA修改了共享变量X的值但还未写回主内存时,另外一个线程BBB又对主内存中同一个共享变量X进行操作,但此时A线程工作内存中共享变量x对线程B来说并不可见,这种工作内存与主内存同步延迟现象就造成了可见性问题
04_可见性的代码验证说明
import java.util.concurrent.TimeUnit;
/**
* 假设是主物理内存
*/
class MyData {
//volatile int number = 0;
int number = 0;
public void addTo60() {
this.number = 60;
}
}
/**
* 验证volatile的可见性
* 1. 假设int number = 0, number变量之前没有添加volatile关键字修饰
*/
public class VolatileDemo {
public static void main(String args []) {
// 资源类
MyData myData = new MyData();
// AAA线程 实现了Runnable接口的,lambda表达式
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "t come in");
// 线程睡眠3秒,假设在进行运算
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 修改number的值
myData.addTo60();
// 输出修改后的值
System.out.println(Thread.currentThread().getName() + "t update number value:" + myData.number);
}, "AAA").start();
// main线程就一直在这里等待循环,直到number的值不等于零
while(myData.number == 0) {}
// 按道理这个值是不可能打印出来的,因为主线程运行的时候,number的值为0,所以一直在循环
// 如果能输出这句话,说明AAA线程在睡眠3秒后,更新的number的值,重新写入到主内存,并被main线程感知到了
System.out.println(Thread.currentThread().getName() + "t mission is over");
}
}
由于没有volatile
修饰MyData
类的成员变量number
,main
线程将会卡在while(myData.number == 0) {}
,不能正常结束。若想正确结束,用volatile
修饰MyData
类的成员变量number
吧。
volatile类比
没有volatile修饰变量效果,相当于A同学拷贝了老师同一课件,A同学对课件进一步的总结归纳,形成自己的课件,这就与老师的课件不同了。
有volatile修饰变量效果,相当于A同学拷贝了老师同一课件,A同学对课件进一步的总结归纳,形成自己的课件,并且与老师分享,老师认可A同学修改后的课件,并用它来作下一届的课件。
05_volatile不保证原子性
原子性指的是什么意思?
不可分割,完整性,也即某个线程正在做某个具体业务时,中间不可以被加塞或者被分割。需要整体完整要么同时成功,要么同时失败。
volatile不保证原子性案例演示:
class MyData2 {
/**
* volatile 修饰的关键字,是为了增加 主线程和线程之间的可见性,只要有一个线程修改了内存中的值,其它线程也能马上感知
*/
volatile int number = 0;
public void addPlusPlus() {
number ++;
}
}
public class VolatileAtomicityDemo {
public static void main(String[] args) {
MyData2 myData = new MyData2();
// 创建10个线程,线程里面进行1000次循环
for (int i = 0; i < 20; i++) {
new Thread(() -> {
// 里面
for (int j = 0; j < 1000; j++) {
myData.addPlusPlus();
}
}, String.valueOf(i)).start();
}
// 需要等待上面20个线程都计算完成后,在用main线程取得最终的结果值
// 这里判断线程数是否大于2,为什么是2?因为默认是有两个线程的,一个main线程,一个gc线程
while(Thread.activeCount() > 2) {
// yield表示不执行
Thread.yield();
}
// 查看最终的值
// 假设volatile保证原子性,那么输出的值应该为: 20 * 1000 = 20000
System.out.println(Thread.currentThread().getName() + "t finally number value: " + myData.number);
}
}
最后的结果总是小于20000。
06_volatile不保证原子性理论解释
number++
在多线程下是非线程安全的。
我们可以将代码编译成字节码,可看出number++
被编译成3条指令。
假设我们没有加 synchronized
那么第一步就可能存在着,三个线程同时通过getfield
命令,拿到主存中的 n值,然后三个线程,各自在自己的工作内存中进行加1操作,但他们并发进行 iadd
命令的时候,因为只能一个进行写,所以其它操作会被挂起,假设1线程,先进行了写操作,在写完后,volatile的可见性,应该需要告诉其它两个线程,主内存的值已经被修改了,但是因为太快了,其它两个线程,陆续执行 iadd
命令,进行写入操作,这就造成了其他线程没有接受到主内存n的改变,从而覆盖了原来的值,出现写丢失,这样也就让最终的结果少于20000。
07_volatile不保证原子性问题解决
可加synchronized解决,但它是重量级同步机制,性能上有所顾虑。
如何不加synchronized解决number++在多线程下是非线程安全的问题?使用AtomicInteger。
import java.util.concurrent.atomic.AtomicInteger;
class MyData2 {
/**
* volatile 修饰的关键字,是为了增加 主线程和线程之间的可见性,只要有一个线程修改了内存中的值,其它线程也能马上感知
*/
volatile int number = 0;
AtomicInteger number2 = new AtomicInteger();
public void addPlusPlus() {
number ++;
}
public void addPlusPlus2() {
number2.getAndIncrement();
}
}
public class VolatileAtomicityDemo {
public static void main(String[] args) {
MyData2 myData = new MyData2();
// 创建10个线程,线程里面进行1000次循环
for (int i = 0; i < 20; i++) {
new Thread(() -> {
// 里面
for (int j = 0; j < 1000; j++) {
myData.addPlusPlus();
myData.addPlusPlus2();
}
}, String.valueOf(i)).start();
}
// 需要等待上面20个线程都计算完成后,在用main线程取得最终的结果值
// 这里判断线程数是否大于2,为什么是2?因为默认是有两个线程的,一个main线程,一个gc线程
while(Thread.activeCount() > 2) {
// yield表示不执行
Thread.yield();
}
// 查看最终的值
// 假设volatile保证原子性,那么输出的值应该为: 20 * 1000 = 20000
System.out.println(Thread.currentThread().getName() + "t finally number value: " + myData.number);
System.out.println(Thread.currentThread().getName() + "t finally number2 value: " + myData.number2);
}
}
输出结果为:
main finally number value: 18766
main finally number2 value: 20000
08_volatile指令重排案例1
计算机在执行程序时,为了提高性能,编译器和处理器的常常会对指令做重排,一般分以下3种:
单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致。
处理器在进行重排序时必须要考虑指令之间的数据依赖性
多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。
重排案例
public void mySort{
int x = 11;//语句1
int y = 12;//语句2
× = × + 5;//语句3
y = x * x;//语句4
}
可重排序列:
- 1234
- 2134
- 1324
问题:请问语句4可以重排后变成第一个条吗?答:不能。
重排案例2
int a,b,x,y = 0
线程1 | 线程2 |
---|---|
x = a; | y = b; |
b = 1; | a = 2; |
x = 0; y = 0 |
如果编译器对这段程序代码执行重排优化后,可能出现下列情况:
线程1 | 线程2 |
---|---|
b = 1; | a = 2; |
x = a; | y = b; |
x = 2; y = 1 |
这也就说明在多线程环境下,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的。
09_volatile指令重排案例2
观察以下程序:
public class ReSortSeqDemo{
int a = 0;
boolean flag = false;
public void method01(){
a = 1;//语句1
flag = true;//语句2
}
public void method02(){
if(flag){
a = a + 5; //语句3
}
System.out.println("retValue: " + a);//可能是6或1或5或0
}
}
多线程环境中线程交替执行method01()
和method02()
,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。
禁止指令重排小总结
volatile实现禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象
先了解一个概念,内存屏障(Memory Barrier)又称内存栅栏,是一个CPU指令,它的作用有两个:
- 保证特定操作的执行顺序,
- 保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)。
由于编译器和处理器都能执行指令重排优化。如果在指令间插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序,也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。内存屏障另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。
对volatile变量进行写操作时,会在写操作后加入一条store屏障指令,将工作内存中的共享变量值刷新回到主内存。
对Volatile变量进行读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量。
线性安全性获得保证
-
工作内存与主内存同步延迟现象导致的可见性问题 - 可以使用synchronized或volatile关键字解决,它们都可以使一个线程修改后的变量立即对其他线程可见。
-
对于指令重排导致的可见性问题和有序性问题 - 可以利用volatile关键字解决,因为volatile的另外一个作用就是禁止重排序优化。
10_单例模式在多线程环境下可能存在安全问题
懒汉单例模式
public class SingletonDemo {
private static SingletonDemo instance = null;
private SingletonDemo () {
System.out.println(Thread.currentThread().getName() + "t 我是构造方法SingletonDemo");
}
public static SingletonDemo getInstance() {
if(instance == null) {
instance = new SingletonDemo();
}
return instance;
}
public static void main(String[] args) {
// 这里的 == 是比较内存地址
System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
}
}
输出结果:
main 我是构造方法singletonDemo
true
true
true
true
但是,在多线程环境运行上述代码,能保证单例吗?
public class SingletonDemo {
private static SingletonDemo instance = null;
private SingletonDemo () {
System.out.println(Thread.currentThread().getName() + "t 我是构造方法SingletonDemo");
}
public static SingletonDemo getInstance() {
if(instance == null) {
instance = new SingletonDemo();
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
SingletonDemo.getInstance();
}, String.valueOf(i)).start();
}
}
}
输出结果:
4 我是构造方法SingletonDemo
2 我是构造方法SingletonDemo
5 我是构造方法SingletonDemo
6 我是构造方法SingletonDemo
0 我是构造方法SingletonDemo
3 我是构造方法SingletonDemo
1 我是构造方法SingletonDemo
显然不能保证单例。
解决方法之一:用synchronized
修饰方法getInstance()
,但它属重量级同步机制,使用时慎重。
public synchronized static SingletonDemo getInstance() {
if(instance == null) {
instance = new SingletonDemo();
}
return instance;
}
11_单例模式volatile分析
解决方法之二:DCL(Double Check Lock双端检锁机制)
public class SingletonDemo{
private SingletonDemo(){}
private volatile static SingletonDemo instance = null;
public static SingletonDemo getInstance() {
if(instance == null) {
synchronized(SingletonDemo.class){
if(instance == null){
instance = new SingletonDemo();
}
}
}
return instance;
}
}
DCL中volatile解析
原因在于某一个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化。instance = new SingletonDemo();
可以分为以下3步完成(伪代码):
memory = allocate(); //1.分配对象内存空间
instance(memory); //2.初始化对象
instance = memory; //3.设置instance指向刚分配的内存地址,此时instance != null
步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变,因此这种重排优化是允许的。
memory = allocate(); //1.分配对象内存空间
instance = memory;//3.设置instance指向刚分配的内存地址,此时instance! =null,但是对象还没有初始化完成!
instance(memory);//2.初始化对象
但是指令重排只会保证串行语义的执行的一致性(单线程),但并不会关心多线程间的语义一致性。
所以当一条线程访问instance不为null时,由于instance实例未必已初始化完成,也就造成了线程安全问题。
12_CAS是什么
Compare And Set
示例程序
public class CASDemo{
public static void main(string[] args){
AtomicInteger atomicInteger = new AtomicInteger(5);// mian do thing. . . . ..
System.out.println(atomicInteger.compareAndSet(5, 2019)+"t current data: "+atomicInteger.