1.volatile是什么?
volatile是java虚拟机提供的轻量级的同步机制。
(1)保证可见性
(2)不保证原子性
(3)禁止指令重排
首先了解一下JMM是什么:
JMM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念并不真实存在,他描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实力字段,静态字段和构成数组对象的元素)的访问方式。
JMM关于同步的规定:
1.线程解锁前,必须把共享变量的值刷新回主内存。
2.线程加锁前,必须读取内存的最新值到自己的内存中。
3.加锁解锁是同一把锁。
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(栈空间)工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作必须在工作内存当中进行,首先要将变量从主内存拷贝在自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存。不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成。
(1)可见性
当某一个线程修改完自己的工作内存的值并且写回给主内存的时候及时通知其他线程.这种通知机制就称之为JMM可见性.
代码验证volatile的可见性
/**
* 没有volatile修饰
*/
class NumberNoVolatile {
private int number = 0;//共享内存
private void change() {
this.number = 60;
}
private int getNumber() {
return number;
}
//主线程
public static void main(String[] args) {
NumberNoVolatile numberNoVolatile = new NumberNoVolatile();
//开启新线程修改number
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "启动");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
numberNoVolatile.change();
System.out.println(Thread.currentThread().getName() + "更新完成");
}).start();
//主线程检测number的值
while (numberNoVolatile.getNumber() <= 0) {
}
//如果有可见性,主线程会收到number值改变的通知,结束循环.
//如果没有可见性,主线程无法得知number值已经改变,循环继续,
System.out.println("检测到number改变,循环结束");
}
}
/**
* 有volatile修饰
*/
class NumberWithVolatile {
private volatile int number = 0;//共享内存
public void change() {
this.number = 60;
}
public int getNumber() {
return number;
}
//主线程
public static void main(String[] args) {
NumberWithVolatile numberWithVolatile = new NumberWithVolatile();
//开启新线程修改number
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "启动");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
numberWithVolatile.change();
System.out.println(Thread.currentThread().getName() + "更新完成");
}).start();
//主线程检测number的值
while (numberWithVolatile.getNumber() <= 0) {
}
//如果有可见性,主线程会收到number值改变的通知,结束循环.
//如果没有可见性,主线程无法得知number值已经改变,循环继续,
System.out.println("检测到number改变,循环结束");
}
}
/**
* 验证volatile的可见性
* number 之前没有volatile关键词修饰
*/
public class Test01 {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
(2)volatile不保证原子性
原子性:某个线程正在做某个具体业务时,中间不可被加塞或者分割,具有完整性.要么同时成功,要么同时失败.
代码验证volatile不保证原子性
/**
* 验证volatile的不保证原子性
*/
public class Test02 {
private volatile int number = 0;//共享内存
private void add() {
this.number++;
}
private int getNumber() {
return number;
}
//主线程
public static void main(String[] args) {
Test02 test02 = new Test02();
//开启新线程修改number
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
test02.add();
}
}).start();
}
//等待以上计算结束
while (Thread.activeCount() > 2) {
Thread.yield();
}
//计算结果应该为20000
System.out.println(test02.getNumber());
}
}
(3)volatile禁止指令重排
计算机在执行程序时,为了提高性能,编译器和处理器会对指令做重排,一般分为以下三种:
源代码->编译器优化的重排->指令并行重排->内存系统的重排->最终执行的指令.
单线程环境里确保程序最终执行结果和代码顺序执行的结果一样.
处理器在进行重排序时必须要考虑指令之间的数据依赖性.
多线程环境中线程交替执行,由于编译器优化的重排存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测.
volatile实现禁止指令重排优化,从而避免多线程环境中程序出现乱序执行的现象.