volatile特性与使用例子

376 阅读2分钟

1、JMM模型

image.png

2、volatile特性

volatile有两大特性:
	(1)保证变量可见性
	(2)防止指令重排序

3、例子

a、volatile保证变量可见性

解释:

​ 保证变量可见性:在一个线程修改了共享变量后会让其它线程的本地缓存失效,取主存重读。

底层实现方式:

​ jvm的内存屏障指令(storeLocal)

例子:
//flag作为跳出循环标识
public class VisibilityTest {
    private boolean flag = true;

    public void refresh() {
        flag = false;
        System.out.println(Thread.currentThread().getName() + "修改flag");

    }

    public void load() {
        System.out.println(Thread.currentThread().getName() + "开始执行.....");
        int i = 0;
        while (flag) {
            i++;
            //TODO 业务逻辑


        }
        System.out.println(Thread.currentThread().getName() + "跳出循环: i=" + i);

    }

    public static void main(String[] args) throws InterruptedException {
        VisibilityTest test = new VisibilityTest();

        // 线程threadA模拟数据加载场景
        Thread threadA = new Thread(() -> test.load(), "threadA");
        threadA.start();

        // 让threadA执行一会儿
        Thread.sleep(1000);
        // 线程threadB通过flag控制threadA的执行时间
        Thread threadB = new Thread(() -> test.refresh(), "threadB");
        threadB.start();
    }
}

结果修改了flag程序也没有退出,因为B线程修改了flag刷回主存的动作A线程不可见,A还是用的本地缓存中的flag,见开头JMM模型

image.png 将flag改成volatile修饰(private volatile boolean flag),运行后结果如下

image.png

b、volatile防止指令重排序

解释:

​ jvm解释代码时在不影响结果的情况下会重排代码先后顺序来优化执行速度。

​ 例:

​ x=3; y=4; z=x+1; 重排后==> y=4; x=3; z=x+1; (重排原因是计算z=x+1时要读入x的值进线程本地缓存,将x=3放在前一步就不用重复读)

底层实现方式:

​ jvm的内存屏障指令(storeLocal)

例子:
public class ReOrderTest {
    private static int x = 0, y = 0;
    private static int a = 0, b = 0;
    public static void main(String[] args) throws InterruptedException{
        int i=0;
        while (true) {
            i++;
            x = 0;
            y = 0;
            a = 0;
            b = 0;

            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    shortWait(20000);
                    a = 1;
                    x = b;

                }
            });
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    b = 1;
                    y = a;
                }
            });

            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
            System.out.println("第" + i + "次(" + x + "," + y + ")");
            if (x==0&&y==0){
                break;
            }
        }
    }
    public static void shortWait(long interval){
        long start = System.nanoTime();
        long end;
        do{
            end = System.nanoTime();
        }while(start + interval >= end);
    }
}

结果会出现:(1,1) (1,0) (0,1) (0,0)

重点关注(0,0)是怎么出现的,按道理说无论线程A、B谁先执行x,y都不会全为0,原因是A或者B在执行时进行了重排序,如A线程 a=1; x=b --》x=b; a=1

将a,b加上volatile修饰后就不会出现 (0,0)结果 (private static volatile int a = 0, b = 0;)

单例模型下volatile的作用:

public class SingleTest {

    private static volatile SingleTest singleTest=null;

    public SingleTest singleInit(){
        if (singleTest==null){
            synchronized (singleTest){
                if (singleTest==null){
                    // 1、获取对象地址 2、地址里对象初始化 3、将singleTest指向地址
                    //如果singleTest没有volatile修饰 1 2 3顺序可重排为 1 3 2,这样多个线程竞争时就会出问题,有可能得到一个没有初始化的singleTest
                    singleTest=new SingleTest();
                }
            }
        }
        return singleTest;
    }
}