做程序员久了,如果你还是写那些crud,那真的应该好好的想一下了。觉得自己如果不了解点别的东西都对不起自己这些年的工作经验,之后出去不是什么10年的工作经验而是一个经验用了10年,不论你出于什么原因学习了jvm,都认为迈出这一步很棒,jvm的介绍我希望用自己的理解表达出来,如果有什么地方说的不好,欢迎大家指正,没准还能认识一下~
今天小编跟大家分享一下volatile,主要从她的定义,特性,原理,使用上进行分析,希望尽可能以小编认为通俗的方式说出来,当然如果有不对的地方,大家踊跃拍砖~
volatile是JVM提供的轻量级同步机制,synchronized也是JVM提供的同步机制,这里为啥说volatile是轻量级呢,正是和synchronized对比得来的,synchronized是重量级的同步机制。volatile不能保证原子性,sync可以保证,至于什么是原子性,就不用我多说了吧。
现在要划重点了,赶紧拿出你的小本本出来,volatile具有的特性是1. 可见性;2禁止指令重排,这个东西就算你不理解,那你也得知道~
可见性说的是,在高并发的情况下,多个线程同时访问一个用volatile修饰的变量,当其中一个线程修改了这个变量的值,其他线程可以立即看到变量的最新值。如果这句话不明白,我们看用代码来说话!
/**
* 验证volatile的特性
* Created by Viola on 2019/7/11.
*/
public class MyData {
int number=0;
public void addT060(){
this.number=60;
}
public void addTo60(){
this.number=60;
}
}
class VolatileDemo{
public static void main(String[] args) {
//测试可见性
seeOkByVolatile();
}
//volatile可以保证可见性,及时通知其他线程,主物理内存的值已经被修改
public static void seeOkByVolatile(){
MyData myData=new MyData();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t come in ");
//暂停一会线程
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
};
myData.addTo60();
System.out.println(Thread.currentThread().getName()+"\t update number value->"+myData.number);
},"childThread:").start();
//第2个线程时main线程
while (myData.number==0){
}
System.out.println(Thread.currentThread().getName()+"\t mission is over,main get number value is :"+myData.number);
}
}
大家可以猜一下这段代码的运行完,最后一行打印的myData.number是啥,嘻嘻我怎么也开始出题了:
A、0;
B、60;
C、以上都不是;
如果将MyData 中的 int number=0;改为volatile int number=0;结果又是A、B、C中的哪一个呢?答案先不揭晓了,大家可以先思考一下,如果想要确定答案或是好奇答案,请留言,小编看到会回复~
上面是表层的,入门级别,也是不够的,人家都不怎么问你啥是可见性了,人家会问你的是volatile可见性是怎么保证的,他的原理是什么?没事别慌,下面原理会说~
我们写的代码顺序并不是真正执行时候的顺序,这个有疑问的举手~编译成.class的二进制文件,在解释执行时,cpu指令会做一些优化,使得顺序会发生改变。禁止指令重排说的是,我们用volatile修饰的这行代码的前后会分别生成一道不可逾越的屏障,volatile修饰的这样代码的前面的代码不会插进来,之后的代码也不会插进来,当然这行代码也不会跑出去。因此禁止指令重排,保证了代吗执行的有序性。
NO,不行!经典的例子 i++;i++分三步 1,读取i,2.加1操作,3结果赋值给i。别多说了,show me the code~
/**
* 验证volatile的特性
* Created by Viola on 2019/7/11.
*/
public class MyData {
volatile int number=0;
//i++,当前是volatile修饰
public void addPlusPlus(){
number++;
}
}
class VolatileDemo{
public static void main(String[] args) {
//测试原子性
notAutomic();
}
//不能保证原子性
public static void notAutomic(){
MyData myData=new MyData();
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int i1 = 0; i1 < 1000; i1++) {
myData.addPlusPlus();
}
},String.valueOf(i)).start();
}
//等待上面的10个线程全部执行完成
while(Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"\t finished , number value is \t"+myData.number);
}
}
这段代码的运行结果我们发现,不是9000多就是8000多,循环了1万次,调用了1万次addPlusPlus(),但是结果没有到1万,但是最后的原子性能不能保证依然不用我多说了~
JMM(Java 内存模型 java memory model简称JMM)本身是一种抽象的概念,描述了一组规范,规定了程序中各个变量(实例字段,静态字段和构成数组对象的元素)的访问方式。
JMM关于同步的规定:
1. 线程解锁前,必须要把共享变量的值刷回到主内存中
2. 线程加锁前,必须读取主内存的最新值刷到自己的工作内存
3. 上面说的加锁和解锁是同一把锁
由于JVM运行程序的实体是线程,每个线程创建时JVM都会为其创建一个工作空间(栈空间),工作空间是每个线程私有的,而java内存模型规定所有变量都存储在主内存中,主内存中的变量是线程共享的,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先将变量从主内存拷贝到工作内存,对变量进行操作,操作完成后写回到主内存中,不能直接操作主内存中的变量,线程间的通信也通过主内存来完成,其简要访问过程如下:
计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排:源代码–>编译器优化的重排–>指令并行的重排–>内存系统的重排–>最终执行的指令。如果两个操作之间的关系不存在下面所列举的先行发生(happen-before)情况,便会出现指令重排:
将上面的总结一下可以得出:1.单线程里能够确保程序最终执行结果和代码顺序执行的结果一致。2、处理器在进行重排序时必须要考虑指令之间的数据依赖性。
哪里用我们可以用到volatile呢?其实我们常见的单例,保证线程安全的单例,便是用到了volatile,话不多说了,代码如下:
/**
* 懒汉模式,单例对象--synchronized
* 双层同步锁,+volatile 禁止指令重排序是线程安全的
* 单例实例在第一次使用时创建
* Created by Viola on 2019/5/8.
*/
@ThreadSafe
public class SingleTonExample5 {
//私有构造函数
private SingleTonExample5(){}
// 1、memory = allocate() 分配对象的内存空间
// 2、ctorInstance() 初始化对象
// 3、instance = memory 设置instance指向刚分配的内存
// JVM和cpu优化,发生了指令重排
// 1、memory = allocate() 分配对象的内存空间
// 3、instance = memory 设置instance指向刚分配的内存
// 2、ctorInstance() 初始化对象
//私有的单例对象
private volatile static SingleTonExample5 instance=null;
//共有的创建对象的方法
public static SingleTonExample5 getInstance(){
if (instance==null){ //A-1
synchronized (SingleTonExample.class){//双层检测机制
if (instance==null){
instance=new SingleTonExample5(); //A-3
}
}
}
return instance;
}
}
懒汉模式的线程安全的单例,对A-3这行做了详细的说明,如果不用volatile修饰,这时有的线程会拿到一个没有初始化完成的对象而出现错误。
希望您能从小编的文章中得到一些感受或者启发,那将是我最大的荣幸,感谢您宝贵的阅读时间。