volatile关键字是Java虚拟机提供的轻量级的同步机制
它具有以下三个特性:
1)保证可见性
2)无法确保原子性
3)禁止指令重排序
线程操作变量的工作流程
1.普通情况下,在多线程环境下,一个线程在完成对变量拷贝副本的修改等操作后,其他线程是无法立即得知的,可能会导致死循环问题。但当该变量被volatile关键字修饰后,某一线程修改了这个变量的值,其他变量会立即得知该变量的值已改变,这就是volatile能保证可见性的体现。
验证代码:
class MyData{
volatile int number = 0;
public void AddTo60(){
this.number = 60;
}
}
public class VolatileDemo {
public static void main(String[] args){
MyData mydata = new MyData();
new Thread(()->{
System.out.println(Thread.currentThread().getName() +"\t come in");
try{
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}
mydata.AddTo60();
System.out.println(Thread.currentThread().getName()+"\t updated number value->"+mydata.number);
},"AAA").start();
//第2个线程就是我们的main线程
//number未加volatile修饰时,此时main线程并不知道主内存中的number值已经等于60,就会一直在这里死循环等待
while(mydata.number == 0){
//main线程就一直在这里等待循环,直到number不等于0
}
System.out.println(Thread.currentThread().getName() +"\t int type,此时number的值为:"+mydata.number);
}
}
未加volatile关键字修饰时,运行结果:
上述代码运行,运行结果:
2.原子性指的是不可分割,完整性,也即某个线程正在做某个具体的业务时,中间不可以被加塞或者分割,需要整体完整, 要么同时成功,要么同时失败。
使用volatile关键字修饰的变量在多线程高并发的情况下,同样是线程不安全。因为在java中运算并不是原子性操作。原因如图,
验证一下,贴代码:
class MyData{
volatile int number = 0;
public void AddPlusPlus()
{
//此时number已经被volatile关键字修饰
number++;
}
}
public class VolatileDemo {
public static void main(String[] args)
{
MyData data = new MyData();
for (int i = 0; i <20 ; i++) {
new Thread(()->{
for (int j = 1; j <=1000 ; j++) {
data.AddPlusPlus();
data.myaddAtomic();
}
},String.valueOf(i)).start();
}
//需要等待上面20个线程全部计算完成后,再利用main线程取得最终的结果值
while(Thread.activeCount() >2){
Thread.yield();
}
//结果并不等于20000,证明volatile关键字不保证原子性
//可能导致写丢失的情况
System.out.println(Thread.currentThread().getName()+"\t Int Type,final number:"+data.number);
}
}
运行结果:值不等于20000,且每次运行结果都不一样。
使用AtomicInteger类,
class MyData{
AtomicInteger atomicInteger = new AtomicInteger();
public void myaddAtomic()
{
atomicInteger.getAndIncrement();
}
}
public class VolatileDemo {
public static void main(String[] args)
{
MyData data = new MyData();
for (int i = 0; i <20 ; i++) {
new Thread(()->{
for (int j = 1; j <=1000 ; j++) {
data.AddPlusPlus();
data.myaddAtomic();
}
},String.valueOf(i)).start();
}
//需要等待上面20个线程全部计算完成后,再利用main线程取得最终的结果值
while(Thread.activeCount() >2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName() +"\t AtomicInteger Type,final number,此时number的值为:"
+data.atomicInteger);
}
}
运行结果,
实际上,++操作在底层编译阶段是进行了三个步骤!
所以,在多线程的情况下,可能会出现丢失写回主内存的值的情况。
为了避免这种情况出现,我们可以使用sychronized关键字修饰方法或者使用AtomicInteger类(推荐使用的)。
3.禁止重排序。什么是重排序?
重排序是指令的重排序。为了提高性能,编译器和处理器常常会对指令做重排序,重排序就会导致多线程执行的时候有数据不一致问题,导致程序结果不是理想结果。
使用volatile关键字修饰后,就不会发生重排序。