Java中volatile修饰引用类型

在Java中,volatile关键字用于修饰变量,用来保证变量在多线程环境下的可见性和顺序性。当我们使用volatile修饰引用类型时,实际上是修饰引用变量本身,而非对象。

1. 引用类型的可见性问题

在多线程环境下,线程之间共享的变量存储在主内存中,每个线程都有自己的工作内存。当一个线程修改了共享变量的值时,其它线程无法立即感知到这个修改,因为每个线程都有自己的工作内存,线程之间的变量值不会自动同步。

这就导致了一个可见性问题:如果一个线程修改了共享变量的值,其它线程如何能够及时地获取到这个新的值呢?这时候就需要使用volatile关键字。

2. volatile关键字的作用

volatile关键字保证了被修饰的变量对所有线程的可见性。当一个线程修改了volatile变量的值时,JVM会立即将该值刷新到主内存中,而其它线程在读取该变量时,会直接从主内存中获取最新的值。

除了可见性,volatile关键字还保证了一定的顺序性。在一个线程内,所有的操作都是有序执行的,但在一个线程的不同操作间的顺序,并没有严格的限制。而通过使用volatile关键字,可以保证某个操作的执行顺序符合我们的预期。

3. volatile修饰引用类型的问题

当我们使用volatile修饰引用类型时,实际上是修饰引用变量本身,而非对象。也就是说,volatile修饰的是引用地址,而不是对象本身。

public class VolatileExample {
    private volatile MyClass myClass = new MyClass();
    
    public void updateMyClass() {
        myClass = new MyClass(); // 创建了一个新的对象
        // 其它操作
    }
    
    public void doSomething() {
        MyClass localClass = myClass; // 读取引用变量到本地变量
        // 其它操作
    }
    
    // ...
}

在上述代码中,VolatileExample类中的myClass变量是一个引用类型,并且使用volatile关键字修饰。当调用updateMyClass方法时,会创建一个新的MyClass对象,并赋值给myClass变量。而在doSomething方法中,会将myClass的值读取到本地变量localClass中。

如果没有使用volatile关键字修饰myClass变量,那么在doSomething方法中读取到的myClass值可能是过期的,因为其它线程可能已经修改了myClass的值。

但是,使用volatile修饰引用类型时,并不能保证引用类型所指向的对象的可见性。也就是说,虽然一个线程修改了引用变量的值,但是其它线程并不会立即感知到这个修改。只有在获取了新的引用变量后,才能读取到新的对象。

4. 示例代码及分析

下面我们通过一个示例代码来进一步理解volatile修饰引用类型的作用。

public class VolatileExample {
    private volatile MyClass myClass = new MyClass();
    
    public void updateMyClass() {
        myClass.setValue(10);
    }
    
    public void doSomething() {
        MyClass localClass = myClass;
        System.out.println(localClass.getValue()); // 输出:0
    }
    
    public static void main(String[] args) {
        final VolatileExample example = new VolatileExample();
        
        Thread threadA = new Thread(() -> {
            example.updateMyClass();
        });
        
        Thread threadB = new Thread(() -> {
            example.doSomething();
        });
        
        threadA.start();
        threadB.start();
    }
}

class MyClass {
    private int value;
    
    public int getValue() {
        return value;
    }
    
    public