volatile关键字解析
volatile:
1.保證可見性
2.禁止重排序
我們先來看看一個問題,關于i=i+1的問題。
首先,他不是一個原子性的操作,我們通常將不可拆分的操作稱為原子操作
而i=i+1需要先在主存中取得i的值,之后復制到高速緩存之中,CPU再從高速緩存中讀取并計算之后存入高速緩存,最后把值再存入內存中。
那么怎么來解決這個問題呢?
有兩種方法
第一種是Lock加鎖,但是一定會損失并發性,加鎖之后其他cpu無法訪問
第二種是保證可見性,即當前cpu對這個數據進行更改時,其他數據也可以看到修改。
這個是通過共享變量來判斷的,將這個數據設置為共享數據,如果這個數據發生改變,其他持有該數據的cpu都會得到通知,將自己持有的作廢之后重新在內存中獲取。
上面我們講了一下關于可見性的問題
?
那么下面我們來聊一聊關于重排序的問題
什么是重排序呢,在遵守happens--before的原則下,操作系統可以對一些指令進行重排序
比如:
a=1
a++
b=1
我們將第二條和第三條語句交換位置,并不會改變結果
但是有時,看上去互不影響的語句交換順序是會影響最終結果的
例如:
//線程1?
int a=1
int flag = true;
?
//線程二
if(flag){
xxxxxxxxxxxx
}
?
如上,我們本來打算是線程1完成所有的初始化操作之后再執行線程二
但是如果重排序,線程一的兩條語句重排之后,那么a還沒有完成初始化,線程二就會執行。
?
如果我們用volatile使得禁止指令重排序
我們會在這條語句上添加內存屏障
即保證這條語句前面的語句都在這條語句之前執行
保證這條語句后面的語句都在這條語句之后執行
但是他前面幾條語句,以及后面幾條語句的執行順序并不保證
?
volatile只能保證內存一致性,而不能保證原子性
保證原子性需要synchronized或者lock
public class Main {public volatile int inc = 0;public void increase() {inc++;}public static void main(String[] args) {final Main test = new Main();for(int i=0;i<10;i++){new Thread(){public void run() {for(int j=0;j<1000;j++)test.increase();};}.start();}while(Thread.activeCount()>1) //保證前面的線程都執行完Thread.yield();System.out.println(test.inc);} }?
最終inc的結果一定小于10000
因為inc++并不是原子性操作
我們前面說過,需要先在主存中取得i的值,之后復制到高速緩存之中,CPU再從高速緩存中讀取并計算之后存入高速緩存,最后把值再存入內存中。
所有比如現在inc=1
當線程1讀取了inc的值,輪到線程二執行,線程二對inc讀取并加1,因為線程1只是讀取,所以線程二對數據的更改線程一并不知道,線程二將2寫入內存,線程一又將讀取的數據自增,最后還是2
本來應該是3 ,結果就出現了偏差
所以要保證正確性,就必須保證操作是原子性的
否則使用synchronized或者lock
?
volatile還有一個經典的使用案例
單例模式中的雙重鎖
public class Singleton { private volatile static Singleton instance = null; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) {// 1 if (instance == null) {// 2 instance = new Singleton();// 3 } } } return instance; } }?
總結
以上是生活随笔為你收集整理的volatile关键字解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Leetcode--49. 字母异味词分
- 下一篇: 使用JDBC进行简单连接