volatile关键字解读
volatile英文单词是不稳定的意思,其实在Java中也是词如其意。 在很多面试的时候,volatile问得比较多,也是比较重要。
介绍
volatile是Jvm虚拟机提供的轻量级同步机制
有如下特性:
保证可见性
说起volatile的可见性,必须要提到的就是JMM(Java Memory Model)Java内存模型,它是一个抽象的概念,只是一个模型,描述一组规范或者说规则。通过这些规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。
JMM有以下规定
- 线程解锁前,必须把共享变量的值刷写到主内存
- 线程加锁前,必须读取主内存的最新值到自己的工作内存
- 加锁和解锁是一把锁
我们知道JVM运行程序的实体是线程,每个线程JVM都会为其创建工作内存(栈),
工作内存是每个线程的私有区域,其他线程处理主内存之外,不能够访问其他线程的工作内存
Java中的所有变量都存储在主内存中,主内存是所有线程共享的区域,但是对变量的操作(读取赋值等)必须在工作内存中进行,不能够直接在主内存中直接操作变量
线程是把主内存中的变量copy一份到各自的工作内存中,在进行操作。线程之间的操作也是由主内存完成。
但是其实还是有个问题,就是如果线程A修改了共享变量X,但是未写到主内存中,线程B又对X进行操作,A对X的操作对B是不可见的。工作内存和主内存之间的延迟会导致可见性的问题。
代码demo
class MyData{
volatile int number = 0;
public void updatenumber20(){
this.number = 20;
}
void numberplus2(){
number++;
}
}
private static void seeOkVolatile() {
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.updatenumber20();
System.out.println(Thread.currentThread().getName()+"\t update number value :"+myData.number);
},"AAA").start();
//第二个线程为主线程-->main
while (myData.number ==0){
//如果number不加volatile那么会一直等待,知道number不等于0
}
System.out.println(Thread.currentThread().getName()+"\t mission is over ");
}
不保证原子性
说到原子性,大家应该不会陌生,在学习数据库的时候也有接触。
原子性就是不可分割,完整性,某个线程在进行某个操作的时候,要么成功,要么失败,就比如银行转账一样。
但是volatile并不会保证原子性,如果没有加锁,那么所有的线程都会争抢资源,如果一个线程操作完成后,可能没有把更新结果返回给主内存,其它的线程就是对其进行操作
解决方法:
- 使用juc下的AtomicInteger
- 家sync
class MyData{
volatile int number = 0;
public void updatenumber20(){
this.number = 20;
}
void numberplus2(){
number++;
}
AtomicInteger atomicInteger = new AtomicInteger();
public void addMyAtomic(){
atomicInteger.getAndIncrement();
}
}
private static void atom() {
MyData myData = new MyData();
for (int i = 0; i <2 ; i++) {
new Thread(() ->{
for (int j = 0; j <10000 ; j++) {
// System.out.println("Thread.activeCount(): "+Thread.activeCount());
myData.numberplus2();
myData.addMyAtomic();
}
},String.valueOf(i)).start();
}
//需要等待上面的29个线程全部计算完成后,再用main线程取得最终的结果值
while (Thread.activeCount() >2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"\t int type finally number value "+myData.number);
System.out.println(Thread.currentThread().getName()+"\t atomicInteger type finally number value "+myData.atomicInteger);
}
//main int type finally number value 17015
//main atomicInteger type finally number value 20000
有序性 禁止指令重排
volatile实现有序性禁止指令重排,从而避免在多线程环境下程序出现乱序执行的情况
在之前先说一下内存屏障(Memorry Barrier) 又被称为内存栅栏,它是一个CPU的指令,主要作用如下
- 保证特定操作的执行顺序
- 保证某些变量的内存可见性(volatile的可见性则利用此特性)
- 强制刷写各种CPU缓存数据,因此在任何CPU上的线程都可以读到数据的最新版本
由于编译器和处理器在执行指令的时候都对指令重排优化,如果在指令间插入一条Memory Brrrier,则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重拍序,也就是说通过插入Memory Barrier指令禁止内存屏障前后的指令重排优化。
volatile在单例模式中很常见
上一篇: 滑动平均算法