多线程:volatile关键字解析

volatile主要的作用就是保证变量的可见性,使用volatile修饰的变量每次修改后会立刻从缓存行刷回主内存,也会使已经加载的失效,所以每次获取的时候都是最新的,但是不能保证原子性。另外一点就是可以防止指令重排序。

一. volatile多线程可见性

如下代码:

public class PrintStringA {
    private boolean isContinuePrint = true;

    public void setContinuePrint(boolean isContinuePrint) {
        this.isContinuePrint = isContinuePrint;
    }

    public void printStringMethod() {
        try {
            while (isContinuePrint == true) {
                System.out.println("run printStringMethod threadName = " +
                        Thread.currentThread().getName());
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();

        }
    }
}
public class RunA {
    public static void main(String[] args) {
        PrintStringA printStringService = new PrintStringA();
        printStringService.printStringMethod();    (1)
        System.out.println("我要停止他 stopThread = " + Thread.currentThread().getName());    (2)
        printStringService.setContinuePrint(false);    (3)
    }
}

执行结果:

run printStringMethod threadName = main
run printStringMethod threadName = main
run printStringMethod threadName = main
run printStringMethod threadName = main

main线程会一直执行下去。因为此时的程序只有一个线程,而方法内的语句是按照顺序执行的,第(1)没有执行完成,第(2)和(3)句就不会执行。解决办法就是使用多线程。

使用多线程后的代码:

public class PrintStringB implements Runnable {
    private boolean isContinuePrint = true;
    public void setContinuePrint(boolean isContinuePrint) {
        this.isContinuePrint = isContinuePrint;
    }
    public void printStringMethod() {
        try {
            while (isContinuePrint == true) {
                System.out.println("run printStringMethod threadName=" +
                        Thread.currentThread().getName());
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();

        }
    }
    @Override
    public void run() {
        printStringMethod();
    }
}
public class RunB {
    public static void main(String[] args) {
        PrintStringB printStringService = new PrintStringB();
        new Thread(printStringService).start();
        System.out.println("我要停止他 stopThread = " + Thread.currentThread().getName());
        printStringService.setContinuePrint(false);
    }
} 

在idea下执行结果如下:

我要停止他 stopThread = main

但是如果运行在-server服务器模式中的JVM时,执行结果如下:

run printStringMethod threadName = main
run printStringMethod threadName = main
run printStringMethod threadName = main
run printStringMethod threadName = main

为什么会这样呢?因为在启动RunB时,变量 isContinuePrint 会存在公共内存(主内存)和线程的私有内存中,为了效率,线程会一直从私有内存中取 isContinuePrint 的值为true,而setContinuePrint更新的是公共内存的变量,所以程序会死循环。解决的办法就是使用volatile。

使用volatile后的代码:

public class PrintStringC implements Runnable {
    private volatile boolean isContinuePrint = true; // 将其设为 volatile 强制从公共内存取值

    public void setContinuePrint(boolean isContinuePrint) {
        this.isContinuePrint = isContinuePrint;
    }

    public void printStringMethod() {
        try {
            while (isContinuePrint == true) {
                System.out.println("run printStringMethod threadName=" +
                        Thread.currentThread().getName());
                Thread.sleep(1000);
            }
            System.out.println("我被停止了");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        printStringMethod();
    }
}
public class RunC {
    public static void main(String[] args) {
        try {
            PrintStringC printStringService = new PrintStringC();
            new Thread(printStringService).start();
            Thread.sleep(3000);
            System.out.println("我要停止他 stopThread = " + Thread.currentThread().getName());
            printStringService.setContinuePrint(false);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行结果:

run printStringMethod threadName=Thread-0
run printStringMethod threadName=Thread-0
run printStringMethod threadName=Thread-0
我要停止他 stopThread = main
我被停止了

通过使用volatile关键字,强制的从公共内存中读取变量的值,增加了实例变量在多个线程间的可见性,

二. volatile的非原子性

变量在内存中的工作过程图

关键词volatile主要使用场合是在多个线程中感知实例变量(只有实例变量是非线程安全的,局部变量是线程安全的)被更改了,并且可以获得最新的值使用,但是不保证操作是原子性的。

例如,进行 count++ 这样的操作,即count = count + 1,拆解如下:

1,从主存中取出count的值放入工作内存,(对应read和load加载)

2,在工作内存中进行计算,(对应use操作和assign赋值)

3,将工作区的结果写入到主存中,(对应store和write)

其中当我们从主存中取出最新的count之后, 可能其他线程也更改了count的值,这样就导致了不同步。所以volatile只可以保证变量读时的可见性。对于多个线程访问同一个实例变量还需要加锁同步。

三. 防止指令重排序

双重锁单例模式中需要使用volatile来修饰实例,就是利用了volatile防止指令重排序的特点

https://blog.csdn.net/fanxing1964/article/details/79465280

 

参考:《Java多线程编程核心技术》

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值