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多线程编程核心技术》