前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何理解volatile

如何理解volatile

作者头像
老马的编程之旅
发布2022-06-22 15:26:30
4210
发布2022-06-22 15:26:30
举报
文章被收录于专栏:深入理解Android

java内存模型与原子性,可见性和有序性

Java内存模型规定所有的变量都是存在主存当中,每个线程都有自己的工作内存。线程对变量的所有操作都必须在自己的工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。

在java中,执行下面这个语句:

代码语言:javascript
复制
int i=3;

执行线程必须先在自己的工作线程中对变量i所在的缓存行进行赋值操作,然后再写入主存当中。而不是直接将数值3写入主存当中。

原子性 对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。

代码语言:javascript
复制
x = 10;        //语句1
y = x;         //语句2
x++;           //语句3
x = x + 1;     //语句4

只有语句1是原子性操作,其他三个语句都不是原子性操作。 语句2实际上包含2个操作,它先要去读取x的值,再将x的值写入工作内存

只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作。

java.util.concurrent.atomic包中有很多类使用了很高效的机器级指令(而不是使用锁)来保证其他操作的原子性。例如AtomicInteger类提供了方法incrementAndGet和decrementAndGet,它们分别以原子方式将一个整数自增和自减。可以安全地使用AtomicInteger类作为共享计数器而无需同步。 另外这个包还包含AtomicBoolean,AtomicLong和AtomicReference这些原子类仅供开发并发工具的系统程序员使用,应用程序员不应该使用这些类。

可见性 可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。

当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,所以对其他线程是可见的,当有其他线程需要读取时,它会去主存中读取新值。

而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。

有序性 在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。 **可以通过volatile关键字来保证一定的“有序性”。**另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。

volatile关键字

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

1.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。 2.禁止进行指令重排序。

代码语言:javascript
复制
//线程1
boolean stop = false;
while(!stop){
    doSomething();
}

//线程2
stop = true;

每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在自己的工作内存当中。那么当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。

volatile修饰之后就变得不一样了: 1.使用volatile关键字会强制将修改的值立即写入主存; 2.使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效; 3.由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。

volatile无法保证对变量的任何操作都是原子性的。 volatile能在一定程度上保证有序性 1.当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行; 2.在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。

正确使用volatile关键字

使用volatile必须具备以下2个条件: 1.对变量的写操作不依赖于当前值 2.该变量没有包含在具有其他变量的不变式中

第一个条件就是不能是自增自减等操作,上文已经提到volatile不保证原子性。 第二个条件我们来举个例子它包含了一个不变式 :下界总是小于或等于上界

代码语言:javascript
复制
public class NumberRange {
    private volatile int lower, upper;
    public int getLower() { return lower; }
    public int getUpper() { return upper; }
    public void setLower(int value) { 
        if (value > upper) 
            throw new IllegalArgumentException(...);
        lower = value;
    }
    public void setUpper(int value) { 
        if (value < lower) 
            throw new IllegalArgumentException(...);
        upper = value;
    }
}

例如,如果初始状态是 (0, 5),同一时间内,线程 A 调用 setLower(4) 并且线程 B 调用 setUpper(3),显然这两个操作交叉存入的值是不符合条件的,那么两个线程都会通过用于保护不变式的检查,使得最后的范围值是 (4, 3),这显然是不对的。

使用volatile主要有两个场景: 状态标志

代码语言:javascript
复制
volatile boolean shutdownRequested;
...
public void shutdown()
 { 
 shutdownRequested = true;
  }
public void doWork() { 
    while (!shutdownRequested) { 
        // do stuff
    }
}

很可能会从循环外部调用 shutdown() 方法 —— 即在另一个线程中 —— 因此,需要执行某种同步来确保正确实现 shutdownRequested 变量的可见性。然而,使用 synchronized 块编写循环要比使用volatile 状态标志编写麻烦很多。由于 volatile 简化了编码,并且状态标志并不依赖于程序内任何其他状态,因此此处非常适合使用 volatile。

双重检查模式 (DCL)

代码语言:javascript
复制
public class Singleton {  
    private volatile static Singleton instance = null;  
    public static Singleton getInstance() {  
        if (instance == null) {  
            synchronized(this) {  
                if (instance == null) {  
                    instance = new Singleton();  
                }  
            }  
        }  
        return instance;  
    }  
}  

总结

如果严格遵循 volatile 的使用条件即变量真正独立于其他变量和自己以前的值 ,在某些情况下可以使用 volatile 代替 synchronized 来简化代码。然而,使用 volatile 的代码往往比使用锁的代码更加容易出错。除了常见2个场景,其他情况还是使用synchronized

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-02-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • java内存模型与原子性,可见性和有序性
  • volatile关键字
  • 正确使用volatile关键字
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档