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關鍵字修飾後,就不會發生重排序。