一、volatile簡(jiǎn)介
Java語(yǔ)言規(guī)范第三版中對(duì)volatile的定義如下:
java編程語(yǔ)言允許線程訪問(wèn)共享變量,為了確保共享變量能被準(zhǔn)確和一致的更新,線程應(yīng)該確保通過(guò)排他鎖單獨(dú)獲得這個(gè)變量。Java語(yǔ)言提供了volatile,在某些情況下比鎖更加方便。如果一個(gè)字段被聲明成volatile,java線程內(nèi)存模型確保所有線程看到這個(gè)變量的值是一致的。
術(shù)語(yǔ)定義
| 術(shù)語(yǔ) | 英文單詞 | 描述 |
|---|---|---|
| 共享變量 | 在多個(gè)線程之間能夠被共享的變量被稱為共享變量。共享變量包括所有的實(shí)例變量,靜態(tài)變量和數(shù)組元素。他們都被存放在堆內(nèi)存中,Volatile只作用于共享變量。 | |
| 內(nèi)存屏障 | Memory Barriers | 是一組處理器指令,用于實(shí)現(xiàn)對(duì)內(nèi)存操作的順序限制。 |
| 緩沖行 | Cache line | 緩存中可以分配的最小存儲(chǔ)單位。處理器填寫緩存線時(shí)會(huì)加載整個(gè)緩存線,需要使用多個(gè)主內(nèi)存讀周期。 |
| 原子操作 | Atomic operations | 不可中斷的一個(gè)或一系列操作。 |
| 緩存行填充 | cache line fill | 當(dāng)處理器識(shí)別到從內(nèi)存中讀取操作數(shù)是可緩存的,處理器讀取整個(gè)緩存行到適當(dāng)?shù)木彺妫↙1,L2,L3的或所有) |
| 緩存命中 | cache hit | 如果進(jìn)行高速緩存行填充操作的內(nèi)存位置仍然是下次處理器訪問(wèn)的地址時(shí),處理器從緩存中讀取操作數(shù),而不是從內(nèi)存。 |
| 寫命中 | write hit | 當(dāng)處理器將操作數(shù)寫回到一個(gè)內(nèi)存緩存的區(qū)域時(shí),它首先會(huì)檢查這個(gè)緩存的內(nèi)存地址是否在緩存行中,如果存在一個(gè)有效的緩存行,則處理器將這個(gè)操作數(shù)寫回到緩存,而不是寫回到內(nèi)存,這個(gè)操作被稱為寫命中。 |
| 寫缺失 | write misses the cache | 一個(gè)有效的緩存行被寫入到不存在的內(nèi)存區(qū)域。 |
實(shí)現(xiàn)原理
有volatile變量修飾的共享變量進(jìn)行寫操作的時(shí)候會(huì)將當(dāng)前處理器緩存行的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存中,這個(gè)寫回內(nèi)存的操作會(huì)引起其它CPU里緩存的該內(nèi)存地址的數(shù)據(jù)無(wú)效。具體實(shí)現(xiàn)細(xì)節(jié)參考這篇文章聊聊并發(fā)(一)深入分析Volatile的實(shí)現(xiàn)原理。
二、適用場(chǎng)景
volatile具有sychronized的可見性和有序性,但是不具備原子性。volatile的可見性確保了所有線程看到volatile聲明的共享變量值是最新的。另外在訪問(wèn)volatile聲明的變量時(shí)線程不會(huì)加鎖,也就不會(huì)引起線程的阻塞,這就使得相對(duì)于sychronized來(lái)說(shuō)它只是輕量級(jí)的同步機(jī)制。在某些情況下,如果讀操作遠(yuǎn)遠(yuǎn)大于寫操作時(shí),使用volatile能夠在性能上優(yōu)于鎖。舉個(gè)volatile不具備原子性的例子:
2.1 volatile非原子性代碼示例(不要這樣做)
package com.game.lll.syn;
/**
* Volatile不具備原子性
* @author liulongling
*
*/
class VolatileExample3{
private volatile int count;
public void inc()
{
count++;
}
public static void main(String[] args) {
final VolatileExample3 test = new VolatileExample3();
for(int i=0;i<100000;i++){
new Thread(){
public void run() {
test.inc();
};
}.start();
}
while(Thread.activeCount()>1) //保證前面的線程都執(zhí)行完
Thread.yield();
System.out.println("count:"+test.count);
}
}
控制臺(tái)輸出:
count:99994
Volatile不具備原子性:在代碼第六行雖然使用了volatile來(lái)修飾count,但是從結(jié)果可以看出count++的次數(shù)等于99994,而我們預(yù)期的結(jié)果是100000.所以volatile不足以使自增count++原子化,除非你能保證只有一個(gè)線程對(duì)變量執(zhí)行寫操作。在上一篇文章中:原子性(一) 我對(duì)出現(xiàn)這種結(jié)果做過(guò)原因分析。
2.2 模式一:狀態(tài)標(biāo)志
package com.game.lll.syn;
class VolatileExample2{
private volatile boolean isShuttingDown = false; // 標(biāo)志服務(wù)是否正在關(guān)閉
public boolean isShuttingDown() {
return isShuttingDown;
}
public void userLogin()
{
if(isShuttingDown)
{
// 正在關(guān)服,拒絕登陸
return;
}
if(!isShuttingDown)
{
shutdown();
}
System.out.println(Thread.currentThread()+"玩家登錄");
}
/**
* 關(guān)閉服務(wù)(斷開所有與玩家的連接并且不接受新玩家連接,保存數(shù)據(jù),關(guān)閉程序)
*/
private void shutdown() {
// 停服處理
// 將在線玩家踢下線,并在關(guān)服過(guò)程中不接受新登陸需求
isShuttingDown = true;
System.out.println(Thread.currentThread()+"關(guān)閉服務(wù)器");
}
public static void main(String[] args) {
final VolatileExample2 test = new VolatileExample2();
for(int i=0;i<100000;i++){
new Thread(){
public void run() {
test.userLogin();
};
}.start();
}
while(Thread.activeCount()>1) //保證前面的線程都執(zhí)行完
Thread.yield();
}
}
控制臺(tái)輸出:
Thread[Thread-0,5,main]關(guān)閉服務(wù)器
Thread[Thread-0,5,main]玩家登錄
在游戲停服處理時(shí),我們會(huì)聲明一個(gè)volatile修飾的bool變量。在上面例子已經(jīng)證明volatile不具有原子性,那么在運(yùn)行過(guò)程中可能會(huì)出現(xiàn)當(dāng)線程一在執(zhí)行userLogin()過(guò)程中,線程二調(diào)用了shutdown()將isShuttingDown布爾變量設(shè)置為true。這時(shí)如果使用volatile ,線程二會(huì)馬上通知線程一isShuttingDown的狀態(tài)發(fā)生改變。
2.3 模式二:開銷較低的“讀-寫鎖” 策略
public synchronized void inc()
{
count++;
}
控制臺(tái)輸出:
count:100000
在讀操作多余寫操作時(shí),我們可以使用synchronized來(lái)保證inc的操作是原子的,并使用volatile保證當(dāng)前結(jié)果的可見性。這樣的策略不用每次在讀取count變量時(shí)只允許一個(gè)線程訪問(wèn)它,使得在性能上支持多線程的volatile性能更好一些。
另外volatile還適應(yīng)以下三個(gè)場(chǎng)景,詳細(xì)參考文章:http://blog.csdn.net/vking_wang/article/details/9982709#t5
只有滿足下面所有的標(biāo)準(zhǔn)后,你才能使用volatile變量:
- 寫入變量時(shí)并不依賴變量的當(dāng)前值;或者能夠確保只有單一的線程改變變量的值。
- 變量不需要與其他的狀態(tài)變量共同參與不變約束
- 訪問(wèn)變量時(shí),沒(méi)有其它的原因需要加鎖
作者:小毛驢,一個(gè)Java游戲服務(wù)器開發(fā)者 原文地址:https://liulongling.github.io/