Java关于volatile的一些问题

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;
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值