Java基础:volatile详解
问:请谈谈你对volatile的理解?
答:volatile是Java虚拟机提供的轻量级的同步机制,它有3个特性:
1)保证可见性
2)不保证原子性
3)禁止指令重排
刚学完java基础,如果有人问你什么是volatile?它有什么作用的话,相信一定非常懵逼…
可能看了答案,也完全不明白,什么是同步机制?什么是可见性?什么是原子性?什么是指令重排?
1、volatile保证可见性
1.1、什么是JMM模型?
要想理解什么是可见性,首先要先理解JMM。
JMM(Java内存模型,Java Memory Model)本身是一种抽象的概念,并不真实存在。它描述的是一组规则或规范,通过这组规范,定了程序中各个变量的访问方法。JMM关于同步的规定:
1)线程解锁前,必须把共享变量的值刷新回主内存;
2)线程加锁前,必须读取主内存的最新值到自己的工作内存;
3)加锁解锁是同一把锁;
由于JVM运行程序的实体是线程,创建每个线程时,JMM会为其创建一个工作内存(有些地方称为栈空间),工作内存是每个线程的私有数据区域。
Java内存模型规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问。
但线程对变量的操作(读取、赋值等)必须在工作内存中进行。因此首先要将变量从主内存拷贝到自己的工作内存,然后对变量进行操作,操作完成后再将变量写会主内存中。
看了上面对JMM的介绍,可能还是优点懵,接下来用一个卖票系统来进行举例:
1)如下图,此时卖票系统后端只剩下1张票,并已读入主内存中:ticketNum=1。
2)此时网络上有多个用户都在抢票,那么此时就有多个线程同时都在进行买票服务,假设此时有3个线程都读入了目前的票数:ticketNum=1,那么接着就会买票。
3)假设线程1先抢占到cpu的资源,先买好票,并在自己的工作内存中将ticketNum的值改为0:ticketNum=0,然后再写回到主内存中。
此时,线程1的用户已经买到票了,那么线程2,线程3此时应该不能再继续买票了,因此需要系统通知线程2,线程3,ticketNum此时已经等于0了:ticketNum=0。如果有这样的通知操作,你就可以理解为就具有可见性。
通过上面对JMM的介绍和举例,可以简单总结下。
JMM内存模型的可见性是指,多线程访问主内存的某一个资源时,如果某一个线程在自己的工作内存中修改了该资源,并写回主内存,那么JMM内存模型应该要通知其他线程来从新获取最新的资源,来保证最新资源的可见性。
1.2、volatile保证可见性的代码验证
在1.1中,已经基本理解了可见性的含义,接下来用代码来验证一下,volatile确实可以保证可见性。
1.2.1、无可见性代码验证
首先先验证下,不使用volatile,是不是就是没有可见性。
package com.koping.test;
import java.util.concurrent.TimeUnit;
class MyData
{
int number = 0;
public void add10() {
this.number += 10;
}
}
public class VolatileVisibilityDemo {
public static void main(String[] args) {
MyData myData = new MyData();
// 启动一个线程修改myData的number,将number的值加10
new Thread(
() -> {
System.out.println("线程" + Thread.currentThread().getName()+"\t 正在执行");
try{
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
}
myData.add10();
System.out.println("线程" + Thread.currentThread().getName()+"\t 更新后,number的值为" + myData.number);
}
).start();
// 看一下主线程能否保持可见性
while (myData.number == 0