Volatile关键字

一、JMM(Java内存模型)

1.1 可见性

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

JMM关于同步的规定:

  1. 线程解锁前,必须把共享变量的值刷新回主内存。
  2. 线程加锁前,必须读取主内存的最新值到自己的工作内存。
  3. 加锁解锁是同一把锁。

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

1.2 图解分析

1.2.1 每一个线程都拥有主内存变量的拷贝,假设三个线程,t1,t2,t3,现在都有一个拷贝变量25

1.2.2 现在t1线程把变量改为37,但是线程之间目前是不知道的,是否已经改动

1.2.3 t1把变量修改后,需要把值写回给主内存,此时主内存也变为了37

1.2.4,这时主内存的值改了,但是,其他线程并不知道,它的改变,仍然还是原来的25。如果想让其他线程变为最新的值,就需要有一种机制,当一个线程对主内存改变后,就需要通知其他线程,来获取最新的值。这个机制,就是所说的可见性。

1.2 原子性

1.2.1 原子性指的是什么意思?

不可分割,完整性,也即某个线程正在做某个具体业务时,中间不可以被加塞或者被分割。需要整体完整要么同时成功,要么同时失败。

volatile不保证原子性。

1.3 VolatileDemo代码演示可见性+原子性代码

1.3.1 可见性

package com.jak.demo.test;


import java.util.concurrent.TimeUnit;

class MyData {
    volatile int number = 0;

    public void addTo60() {
        this.number = 60;
    }
}

/**
 * 1 验证volatile的可见性
 *  1.1 假如int number = 0; number变量之前根本没有添加volatile关键字修饰
 */
public class VolatileDemo {

    // main是一切方法的运行入口
    public static void main(String[] args) {
        // 资源类
        MyData myData = new MyData();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t come in");
            //暂停一会儿线程
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myData.addTo60();
            System.out.println(Thread.currentThread().getName() + "\t updated number value: " + myData.number);
        }, "AAA").start();
        //第2个线程就是我们的main线程
        while(myData.number == 0) {
            // main线程就一直在这里等待循环,直到number值不再等于零。
        }
        System.out.println(Thread.currentThread().getName() + "\t mission is over, main get number value: " + myData.number);
    }

}

1.3.2 可见性存在的问题

       通过前面对JMM的介绍,我们知道,各个线程对主内存中共享变量的操作都是各个线程各自拷贝到自己的工作内存进行操作后再写回到主内存中的。

      这就可能存在一个线程AAA修改了共享变量X的值,但还未写回主内存时,另外一个线程BBB又对主内存中同一个共享变量X进行操作,但此时A线程工作内存中共享变量X对线程B来说并不可见,这种工作内存与主内存同步延迟现象就造成了可见性问题。

1.3.3 原子性验证

package com.jak.java.test.volatile1;

class MyData {
	volatile int number = 0;

	//请注意,此时number前面是加了volatile关键字修饰的,volatile不保证原子性
	public void addPlusPlus() {
		number++;
	}
}

public class VolatileDemo {
	public static void main(String[] args) {
		MyData myData = new MyData();

		//启动20个线程
		for (int i = 0; i <= 20; i++) {
			new Thread(() -> {
				//每个线程进行1000次add++操作
				for (int j = 1; j <= 1000; j++) {
					myData.addPlusPlus();
				}
			}, String.valueOf(i)).start();
		}

		//需要等待上面20个线程都全部计算完成后,再用main线程取得最终的结果值看是多少?
		while (Thread.activeCount() >2) {
			Thread.yield();
		}
		System.out.println(Thread.currentThread().getName() + "\t finally number value: " + myData.number);
	}
}

1.3.4 volatile不保证原子性解释

1.3.5 如何解决原子性?

1. 加synchronized

2.使用JUC下的AtomicInteger保证原子性

1.4 有序性

计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排,一般分为以下3种。

单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致。

处理器在进行重排序时,必须要考虑指令之间的数据依赖性

多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。

1.4.1 重排1

1.4.2 重排2

这也就说明在多线程环境下,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的。

1.4.3 重排3

1.4.4 禁止指令重排小结

1.5 线程安全性获得保证

上面的三种使得线程安全性获得保证

工作内存与主内存延迟现象导致的可见性问题

可以使用synchronized或volatile关键字解决,它们都可以使一个线程修改后的变量立即对其他线程可见。

对于指令重排导致的可见性问题和有序性问题

可以利用volatile关键字解决,因为volatile的另外一个作用就是禁止重排序优化。

二、volatile是Java虚拟机提供的轻量级的同步机制

轻量级的synchronize

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

三、你在哪些地方用到过volatile

3.1 单例模式DCL代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值