JMM模型与volatile关键字
一.JMM内存模型
1.1 什么是JMM内存模型
多个线程在使用同一个共享变量的时候:
第一步:将共享变量从主内存里面拷贝一个副本到自己的工作内存当中,在自己的工作内存里面对这个共享变量的副本进行修改。
第二步:在修改了共享变量的值之后,将新的值写回到主内存当中。
第三步:主内存中的共享变量的值修改了之后要及时通知其他的线程,将自己工作内存中的值更新,修改成主内存中的最新值。
这个“及时通知”的机制,就是JMM内存模型的可见性。(主物理内存中共享变量的值的修改对所有线程是及时可见的。)
1.2 什么是volatile关键字
volatile关键字是java虚拟机提供的轻量级的同步机制;
volatile关键字三大特性:保证可见性,不保证原子性,禁止指令重排。
二.volatile关键字保证可见性
2.1可见性的含义:
在java里面,每个线程在工作的时候都会有自己的工作空间,对于线程之间的共享变量来说,线程是这样操作他们的,首先先将共享变量的值从主内存里面拷贝一个副本到自己的工作空间,然后在自己的工作空间里面对这个副本进行操作,然后每次修改完副本的值之后都会立刻将副本的值写回主内存,将主内存里面共享变量的值进行更新,之后主内存又会马上通知其他线程,更新这个共享变量的值,这个过程实现的就是JVM的可见性;概括起来说就是:主线程里面的共享变量的修改,对所有线程都是及时可见的。
2.2 代码验证
package InterviewTest;
class MyData{
volatile int number = 0;
public void addTo60() {
this.number = 60;
}
}
public class VolatileDemo {
public static void main(String[] args) {
MyData myData = new MyData();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" come in");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.addTo60();
System.out.println(Thread.currentThread().getName()+
" updated number value:"+myData.number);
},"VolatileDemo").start();
/*如果不对number加volatile关键字的话,在VolatileDemo线程中对number进行修改,
* 结果是对main线程不可见的;number原本是0,在VolatileDemo线程的工作内存中修改成了60,
* 然后把修改后的值写回主线程,但是没有实现可见性,不能及时通知main线程修改自己工作内存
* 中number的值,所以在main线程的工作内存中,number的值仍然是0,
* 因此下面的while循环的判断条件一直是true,无法退出循环。
* 对number加volatile关键字之后,VolatileDemo线程在工作内存中将number的值修改之后
* 写回主内存中,然后能够马上告知main线程,修改自己工作内存中number的值,
* 然后就达到了在VolatileDemo线程中对共享变量进行修改,能够在其他线程中看见,
* 这就是JMM的可见性。!
*/
while(myData.number==0) {}
System.out.println(Thread.currentThread().getName()
+" mission is over:"+myData.number);
}
}
三.volatile关键字不保证原子性
3.1原子性指的是什么意思?
不可分割,完整性,也即某个线程在做某个具体的业务时,中间不可以被加塞或者被分割。需要整体完整,要么同时成功,要么同时失败。
3.2 验证:
package InterviewTest;
import java.util.concurrent.atomic.AtomicInteger;
/*
* volatile不能保证原子性的验证
*
*/
class MyData2{
volatile int number = 0;//synchronized
public void addTo60() {
this.number=60;
}
public void addPlus() {//synchronized
number++;
}
AtomicInteger atomicInteger = new AtomicInteger();
public void addAtomic() {//原子性的自动增长1
//Atomically increments by one the current value.
atomicInteger.getAndIncrement();
}
}
public class VolatileDemo2 {
public static void main(String[] args) {
MyData2 myData = new MyData2();
for(int i=0;i<20;i++) {
new Thread(()-> {
for(int j=1;j<=1000;j++) {
myData.addPlus();
myData.addAtomic();
}
},"线程:"+String.valueOf(i)).start();
}
//需要等待上面20个线程全部计算完之后,再用main线程取得最终的结果,看结果只是多少
while(Thread.activeCount()>2) {//判断上面20个线程有没有执行完
Thread.yield();//礼让线程;main线程礼让其他线程执行
}
System.out.println(Thread.currentThread().getName()
+" int finally number value:"+myData.number);
System.out.println(Thread.currentThread().getName()
+" AtomicInteger finally number value:"+myData.atomicInteger);
}
}
解决volatile关键字不保证原子性的方式:
(1)直接使用JUC中的AtomicInteger类型。
(2)在方法定义的时候加上synchronized关键字
四.volatile关键字之禁止指令重排
不会,因为有“数据依赖性”,y和x