Java关于volatile的一些问题

Wesley13
• 阅读 607

1. 开始

仔细说volatile是一个复杂的问题,可以从Java内存模型聊到缓存一致性协议,很难界定学到什么地方为止。

很多时候,我们并不需要那么复杂,我们需要更加实用。

所以,下面我们就来聊聊volatitle在实际开发中的问题。

2. 并发编程中的三个重要概念

  1. 原子性:是指一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
  2. 可见性:是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
  3. 有序性:是指程序执行的顺序按照代码的先后顺序执行

volatitle只保证:可见性和有序性

什么意思?下面简单聊聊。

3. 可见性

Java内存模型中,JVM会为每一个线程分配一个本地缓冲区(TLAB,Thread Local Allocation Buffer)。

Java线程会现在自己的缓冲区内修改数据,然后同步到主内存中。

有缓存就可能存在数据一致性问题。

和我们通过网页修改同一份文档是一个道理,我们怎样保证自己修改的东西别人没有修改?

一个典型的代码例子如下:

private boolean init = false;
public void init() {
    init = true;
}

public void doSomething(){
    if(init){
        System.out.println("dosomething");
    }
}

一个线程调用实例方法init修改了init成员变量,但是这个修改是在自己的内存中进行,如果有另一个线程调用doSomething,它的缓冲区中缓存了init变量,它看不到其他线程修改。

同样和在线多人编辑同一文档一样,在没有提交之前,每个人都不知道其他人修改了什么。

如果加上volatitle关键字则不同:

private volatitle boolean init = false;

每个线程在修改了volatitle变量之后,都会马上把这个修改同步到主内存,每个线程在读取volatitle变量的时候都会先去主内存看这个变量有没有被其他变量修改。

4. 有序性

为了获得较好的执行性能,Java没有限制编译器对指令进行重排序。

啥意思?就是下面的方法中,编译之后,init=true可能比a=1先执行。重排序规则又是一个复杂的问题,这里我们只需要知道,指令可能存在重排序。

private int a = 0;
private boolean init = false;

public void init() {
    a = 1;
    init = true;
}

那么问题来了:

public class VolatitleTest {

    private int a = 0;
    private boolean init = false;
    public void init() {
        a = 1;
        init = true;
    }

    public void calc() {
        if (init) {
            System.out.println(a * a);
        }else {
            System.out.println(-1);
        }
    }
}

如果多个线程,同时调用同一个VolatitleTest实例的init,calc方法的结果就不唯一。

重排序这个问题不好复现,但是我们需要知道这个问题的确存在,最典型的例子就是单例模式:

public class Singleton{
    private volatile static Singleton instance = null;
     
    private Singleton() {
         
    }
     
    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}

Singleton必须使用volatitle关键字,否则是有问题的,因为类的创建是多个步骤,有兴趣的朋友可以直接研究一下。

5. volatitle不保证原子性

最重要的问题:volatitle不保证原子性

典型的例子是:

private volatile int a = 0;

public void increace(){
    a++;
}

尽管成员变量使用volatitle修饰,但是increace不是线程非安全的,通常它累加出来的值比实际值小,所以如果要求是精确值得使用CAS或者synchronized。

例如,JDK的AtomicXXX都是使用CAS的方式:

private volatile int value;

public final boolean compareAndSet(boolean expect, boolean update) {
    int e = expect ? 1 : 0;
    int u = update ? 1 : 0;
    return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}

Tomcat中的实现,基本使用的synchronized方式:

protected volatile int maxActive=0;

@Override
public void add(Session session) {
    sessions.put(session.getIdInternal(), session);
    int size = getActiveSessions();
    if( size > maxActive ) {
        synchronized(maxActiveUpdateLock) {
            if( size > maxActive ) {
                maxActive = size;
            }
        }
    }
}
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Python进阶者 Python进阶者
9个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这