JMM(Java Memory Model)
每个Java线程都有⾃⼰的⼯作内存。操作数据,⾸先从主内存中读,得到⼀份拷⻉,操作完毕后再写回到主内存。
由于JVM运⾏程序的实体是线程,⽽每个线程创建时JVM都会为其创建⼀个⼯作内存⼯作内存是每个线程的私有数据区域,⽽Java内存模型中规定所有变量都存储在
主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必
须在⼯作内存中进⾏,⾸先要将变量从主内存拷⻉到⾃⼰的⼯作内存空间,然后对变量进⾏操
作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的⼯作内存中
存储着主内存中的变量副本拷⻉,因此不同的线程间⽆法访问对⽅的⼯作内存,线程间的通信
必须通过主内存来完成,这样就会导致可⻅性、 原⼦性和有序性问题
可⻅性:就是某个线程对主内存内容的更改,应该⽴刻通知到其它线程。
原⼦性:是指⼀个操作是不可分割的,不能执⾏到⼀半,就不执⾏了,i++,看上去是一条指令但是翻译成汇编指令后就变成了3条指令,volatile是不能保证原子性的
有序性:就是指令是有序的,不会被重排。cpu由于每秒可以执行10亿条指令,为了提高效率,cpu有可能会将两条毫无关系的指令(也就是说两条指令交换后不会影响到结果)进行排序
volatile的三大特性:
volatile可以保证可⻅性,及时通知其它线程主物理内存的值已被修改,某个线程对变量的修改,会⽴刻反映到主内存上
原⼦性指的是什么意思?
不和分割,完整性,也即某个线程正则做某个具体业务时,中间不可以被被分割或者加上其他代码。,要么同时成功,要么同时失败
i++翻译成汇编指令:
2: getfield 5: iconst_1 6: iadd 7: putfield | #2 // Field number:I //读 //++常量1 //加操作 #2 // Field number:I //写操作 |
所以加上volatile也不能保证原子性,会导致多个线程操作i++达不到预想的结果,比如第一个线程还没有执行玩iadd,第二个线程进行读取,此时读取到的还是0,并不是第一个线程累加后的结果,所以多次累加后会导致结果变少,最主要的原因还是因为这个i++操作不是原子性的,如果要解决此类问题,用AtomicInteger可以解决问题,底层是通过cas来解决的。
volatile为什么能禁止指令重排?
CPU的jsr内存屏障(Memory Barrier)指令来实现的,包括ss,sl,ls,ll它有两个作⽤
⼀个是保证特定操作的顺序性
⼆是保证变量的可⻅性
也就是说:对volatile修饰的变量进行写操作,会在写操作后加上一个store指令凭证禁止器发生排序
dcl:double check lock修饰的变量一定要加上volatie
原理在之前jvm篇章已经详细分析过