JUC02_详解volatile关键字

①. 什么是volatile

1>. 什么是volatile?

  • ①. volatile是Java虚拟机提供的 轻量级的同步机制(乞丐版的synchronized)

  • ②. 特征:

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排

②. JMM的理解

2>. JMM的理解(java内存模型)

  • ①. JMM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念 并不真实存在,它描述的是一组规则或规范通过规范定制了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式.

  • ②. JMM关于同步规定:

  1. 线程解锁前,必须把共享变量的值刷新回主内存
  2. 线程加锁前,必须读取主内存的最新值到自己的工作内存
  3. 加锁解锁是同一把锁
  • ③. 原理的理解:
    由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方成为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作空间,然后对变量进行操作,操作完成再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存储存着主内存中的变量副本拷贝,因此不同的线程无法访问对方的工作内存,此案成间的通讯(传值) 必须通过主内存来完成,其简要访问过程如下图:
    在这里插入图片描述

③. volatile保证可见性详解

3>. volatile保证可见性详解

  • ①. 原理的理解:
    由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方成为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作空间,然后对变量进行操作,操作完成再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存储存着主内存中的变量副本拷贝,因此不同的线程无法访问对方的工作内存,此案成间的通讯(传值) 必须通过主内存来完成,其简要访问过程如下图:
    在这里插入图片描述

  • ②. 代码演示

	/*
	 验证volatile的可见性:
	 1.加入int number=0; number变量之前没有添加volatile关键字修饰,没有可见性
	 2.添加了volatile,可以解决可见性问题
	* */
	class Resource{
	    //volatile int number=0;
	    volatile int number=0;
	
	    public void addNumber(){
	        this.number=60;
	    }
	}
	public class Volatile_demo1 {
	    public static void main(String[] args) {
	        Resource resource=new Resource();
	         new Thread(()->{
	             System.out.println(Thread.currentThread().getName()+"\t coming ");
	             try {TimeUnit.SECONDS.sleep(4);}catch (InterruptedException e){e.printStackTrace();}
	             resource.addNumber();
	             System.out.println(Thread.currentThread().getName()+"\t update "+resource.number);
	         },"线程A").start();
	
	         //如果主线程访问resource.number==0,那么就一直进行循环
	         while(resource.number==0){
	
	         }
	         //如果执行到了这里,证明main现在通过resource.number的值为60
	        System.out.println(Thread.currentThread().getName()+"\t"+resource.number);
	
	    }
	}
  • ③. 对以上代码的详解

在这里插入图片描述

④. Volatile不保证原子性

4>. Volatile不保证原子性

在这里插入图片描述

  • ①. 代码演示
public class Volatile_demo3 {
    public static void main(String[] args) {
       /* System.out.println(Thread.activeCount());*/
        AutoResource autoResource=new AutoResource();
        //20个线程每个线程循环100次
        for (int i = 1; i <=20; i++) {
            new Thread(()->{
                for (int j = 1; j <=100; j++) {
                    autoResource.numberPlusPlus();
                    autoResource.addAtomicInteger();
                }
            },String.valueOf(i)).start();
        }
        //需要等待上面20个线程都全部计算完后,再用main线程取得的最终的结果值是多少
        //默认有两个线程,一个main线程,二是后台gc线程
         while(Thread.activeCount()>2){
             Thread.yield();
         }
        System.out.println(Thread.currentThread().getName()+"\t int type"+autoResource.number);
        System.out.println(Thread.currentThread().getName()+"\t AutoInteger type"+autoResource.atomicInteger.get());
    }
}
class AutoResource{
    volatile int number=0;

    public void numberPlusPlus(){
        number++;

    }
    //使用AutoInteger保证原子性
    AtomicInteger atomicInteger=new AtomicInteger();

    public void addAtomicInteger(){
        atomicInteger.getAndIncrement();
    }
}

在这里插入图片描述

  • ②. 解释不保证证原子性

在这里插入图片描述

  • ③. 关于n++源码解析
    在这里插入图片描述
  • ④. 解决方案:
	1.使用synchronized
	2.使用AtomicInteger(推荐)
	原理部分在CAS部分有解释
	  AtomicInteger atomicInteger=new AtomicInteger();
	  public void addAtomicInteger(){
	       atomicInteger.getAndIncrement();
	   }

⑤. 禁止指令重排(有序性)

5>. 禁止指令重排(有序性)

  • ①. 计算机在执行程序时,为了提高性能,编译器和处理器常常会做指令重排,一把分为以下3中
    在这里插入图片描述
  • ②. 代码演示重排1
	public void mySort(){
	    int x=11;//语句1
	    int y=12;//语句2
	    x=x+5;//语句3
	    y=x*x;//语句4
	}
	1234
	2134
	1324
	问题:
	请问语句4 可以重排后变成第一条码?
	存在数据的依赖性 没办法排到第一个
  • ③. 重排2
    在这里插入图片描述

  • ④. 加上volatile关键字的时候会禁止指令重排,编程会按照指定的顺序执行

⑥. 你在哪些地方用到过volatile?

6>. 你在哪些地方用到过volatile?

  • ①. 单例模式DCL代码
	public class SingletonDemo {
	
	    private static volatile SingletonDemo instance=null;
	    private SingletonDemo(){
	        System.out.println(Thread.currentThread().getName()+"\t 构造方法");
	    }
	
	    /**
	     * 双重检测机制
	     * @return
	     */
	    public static SingletonDemo getInstance(){
	        if(instance==null){
	            synchronized (SingletonDemo.class){
	                if(instance==null){
	                    instance=new SingletonDemo();
	                }
	            }
	        }
	        return instance;
	    }
	
	    public static void main(String[] args) {
	        for (int i = 1; i <=10; i++) {
	            new Thread(() ->{
	                SingletonDemo.getInstance();
	            },String.valueOf(i)).start();
	        }
	    }
	} 
  • ②. 出现线程不安全原因的分析如下:

原因:
DCL(双端检锁) 机制不一定线程安全,原因是有指令重排的存在,加入volatile可以禁止指令重排原因在于某一个线程在执行到第一次检测,读取到的instance不为null时,instance的引用对象 可能没有完成初始化.
instance=new SingletonDem(); 可以分为以下步骤(伪代码)
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实例未必完成初始化,也就造成了线程安全问题

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值