JMM内存模型

一、volatile的理解

volatile是Java虚拟机提供的轻量级的同步机制
volatile的三大特性:保证可见性,不保证原子性,禁止指令重排

二、JMM(Java内存模型)

JMM(Java内存模型Java Memory Model)本身是一种抽象的概念并不真实存在,在java中,所有实例域、静态域和数组元素存储在堆内存中,堆内存在线程之间共享(本文使用“共享变量”这个术语代指实例域,静态域和数组元素)。局部变量(Local variables),方法定义参数(java语言规范称之为formal method parameters)和异常处理器参数(exception handler parameters)不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。

  1. 可见性

Java线程之间的通信由Java内存模型(Java Memory Model本文简称为JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。Java内存模型的抽象示意图如下:
在这里插入图片描述
从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:

首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
然后,线程B到主内存中去读取线程A之前已更新过的共享变量。
下面通过示意图来说明这两个步骤:
在这里插入图片描述

//可见性测试
public class VolatileTest {

    public static void main(String[] args) {
        vTest v=new vTest();
        Thread thread = new Thread(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(60);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                v.change();
                System.out.println(Thread.currentThread().getName() + ":" + v.a);
        });
        thread.start();
       while (v.a ==0){

       }
       System.out.println(Thread.currentThread().getName()+":"+v.a);
    }

}
class vTest{
    //volatile int a = 0;
    int a = 0;
    public void change(){
        this.a = this.a +1;
    }
}
  1. 原子性:number++在多线程中就算加了volatile也不是安全的.
    解决方法:第一种加sync关键字,第二种使用juc.AtomicInteger。
    //原子性测试
    AtomicInteger atomicInteger = new AtomicInteger();
    public static void main(String[] args) {
        syntest s= new syntest();
        Thread[] threads = new Thread[10];
        for(int i=0;i<10;i++){
            threads[i] = new Thread(()->{
//                synchronized (s){
//                    for(int j=0;j<10;j++){
//                        try {
//                            Thread.sleep(10);
//                        } catch (InterruptedException e) {
//                            e.printStackTrace();
//                        }
//                        s.setA(s.getA()+1);
//                        System.out.println(Thread.currentThread().getName()+":"+s.getA());
//                    }
//                }
                for(int j=0;j<100;j++){
                    s.atomicInteger.getAndIncrement();
                  System.out.println(Thread.currentThread().getName()+":"+s.atomicInteger);
                }
            });
        }
        Arrays.asList(threads).forEach(t->t.start());
    }

3.有序性
指令重排:
在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。重排序分三种类型:
①编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可 以重新安排语句的执行顺序。
②指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存 在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
③内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载 和存储操作看上去可能是在乱序执行。
从java源代码到最终实际执行的指令序列,会分别经历下面三种重排序:
在这里插入图片描述
利用volatile指令禁重排的特性,保证在多线程环境下程序运行的有序性。

public class Singleton5 {
    private Singleton5(){}
    private static volatile Singleton5 instance;
    public static Singleton5 getInstance(){
        if(instance == null){
            //加上同步锁
            synchronized (Singleton5.class){
                if(instance == null){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    instance = new Singleton5();
                }
                return instance;
            }
        }
        return instance;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值