目录
1.volatile的特性
①保持线程可见性
2.volatile注意事项及适用场景
①注意事项
②适用场景
1.volatile的特性
①保持线程可见性
volatile,译为"易变的".
对此我们就可以这样理解,对于被volatile修饰的变量的数值,是容易变化的.
在之前的线程安全文章中,我们有讲解过"可见性",对于线程间的这个特性可能会导致:线程在对主内存中的共享元素做上"无用功",即是执行了一次任务,结果却原封不动甚者造成了更错误的结果.
对于线程可见性导致的线程不安全,我们就可以采用volatile关键字,对共享元素进行修饰.从而达到我们期望的结果.
在jvm中,编译器可能会偷偷的把你写的代码进行优化.这种优化对于单线程来说,只会提高你工作的效率以达到更好的效果.但在多线程的环境下,可能就会发生我们意想不到的错误.
import java.util.Scanner;
public class Test {
public static int n = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread0 = new Thread(() ->{
while(n == 0){
//在while循环中为空,while在短时间内就会执行非常多次
//每一次的循环,都要从表达式中拿n与0进行比较
//因为在短时间内,执行了无数次while,同时也执行了无数次的n与0比较
//编译器发现,n每一次都是0.于是开始优化,
//往后的每一次读取都不会再从主内存中重新读取,而只是读取线程工作内存中的n的副本
//线程1在更新了n变量后,线程0读取n变量的时候却已经不会再从主内存中拿取
//这就导致n变量更新为非0后,程序也不会停止下来
}
System.out.println("END");
});
Thread thread1 = new Thread(() ->{
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
});
thread0.start();
thread1.start();
}
}
输入非0数值后,代码还在不断的运行着
还可以从jconsloe中观察到,线程0一直处在runnable状态下.
而经volatile修饰后的变量,就不会发生这一情况.
每次的读取被修饰的变量,都会重新读取主内存进行拷贝.从而可能保证线程与主内存同一个变量的数值永远相同.就不会发生因可见性而引起的问题.
public static volatile int n = 0;//更改后,上面的代码能够恢复到我们预期的结果
2.volatile注意事项及适用场景
①注意事项
有重要的一点,我们要知道.volatile并不能保持原子性.
对于volatile,能做的只是保证在一个线程在工作内存中的从主内存拷贝到的共享变量的副本与主内存中的共享变量的数值一致.
上面的场景,就是因为没有保证原子性而导致的.
②适用场景
volatile只适用于,文章中的第一个代码.一个线程写入,一个不同的线程进行读取的场景下.
如果两个线程都要读取与写入同一个变量,还是要对其进行上锁,使用synchronized关键字