JMM內(nèi)存抽象
JMM定義了線程和主內(nèi)存之間的抽象關系:線程之間的共享變量存儲在主內(nèi)存中列肢,每個線程都有一個私有的本地內(nèi)存迹恐,本地內(nèi)存中存儲了該線程以讀/寫共享變量的副本逗载。
本地內(nèi)存是JMM的一個抽象概念批什,并不真實存在农曲。它涵蓋了緩存,寫緩沖區(qū),寄存器以及其他的硬件和編譯器優(yōu)化乳规。
同時JMM確保在不同的編譯器和不同的處理器平臺之上形葬,通過禁止特定類型的編譯器重排序和處理器重排序,為程序員提供一致的內(nèi)存可見性保證
所有實例域暮的、靜態(tài)域和數(shù)組元素存儲在堆內(nèi)存中笙以,堆內(nèi)存在線程之間共享
局部變量,方法定義參數(shù)和異常處理器參數(shù)不會在線程之間共享冻辩,它們不會有內(nèi)存可見性問題猖腕,也不受內(nèi)存模型的影響
重排序
在執(zhí)行程序時為了提高性能,編譯器和處理器常常會對指令做重排序
- 編譯器優(yōu)化的重排序:編譯器在不改變單線程程序語義的前提下恨闪,可以重新安排語句的執(zhí)行順序
- 指令級并行的重排序:現(xiàn)代處理器采用了指令級并行技術(Instruction-Level Parallelism倘感, ILP)來將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性咙咽,處理器可以改變語句對應機器指令的執(zhí)行順序
- 內(nèi)存系統(tǒng)的重排序:由于處理器使用緩存和讀/寫緩沖區(qū)老玛,這使得加載和存儲操作看上去可能是在亂序執(zhí)行
- 對于編譯器:JMM的編譯器重排序規(guī)則會禁止特定類型的編譯器重排序
- 對于處理器重排序:JMM的處理器重排序規(guī)則會要求java編譯器在生成指令序列時,插入特定類型的內(nèi)存屏障(memory barriers)指令钧敞,通過內(nèi)存屏障指令來禁止特定類型的處理器重排序
內(nèi)存屏障
為了保證內(nèi)存可見性蜡豹,java編譯器在生成指令序列的適當位置會插入內(nèi)存屏障指令來禁止特定類型的處理器重排序
屏障類型 | 指令示例 | 說明 |
---|---|---|
LoadLoad Barriers | Load1; LoadLoad; Load2 | 確保Load1數(shù)據(jù)的裝載,之前于Load2及所有后續(xù)裝載指令的裝載 |
StoreStore Barriers | Store1; StoreStore; Store2 | 確保Store1數(shù)據(jù)對其他處理器可見(刷新到內(nèi)存)溉苛,之前于Store2及所有后續(xù)存儲指令的存儲 |
LoadStore Barriers | Load1; LoadStore; Store2 | 確保Load1數(shù)據(jù)裝載镜廉,之前于Store2及所有后續(xù)的存儲指令刷新到內(nèi)存 |
StoreLoad Barriers | Store1; StoreLoad; Load2 | 確保Store1數(shù)據(jù)對其他處理器變得可見(指刷新到內(nèi)存),之前于Load2及所有后續(xù)裝載指令的裝載炊昆。StoreLoad Barriers會使該屏障之前的所有內(nèi)存訪問指令(存儲和裝載指令)完成之后桨吊,才執(zhí)行該屏障之后的內(nèi)存訪問指令 |
StoreLoad Barriers是一個“全能型”的屏障,它同時具有其他三個屏障的效果凤巨。執(zhí)行該屏障開銷會很昂貴视乐,因為當前處理器通常要把寫緩沖區(qū)中的數(shù)據(jù)全部刷新到內(nèi)存中(buffer fully flush)
Happen-Before原則
JMM通過這個概念來闡述操作之間的內(nèi)存可見性:如果一個操作執(zhí)行的結果需要對另一個操作可見,那么這兩個操作之間必須存在happens-before關系
兩個操作既可以是在一個線程之內(nèi)敢茁,也可以是在不同線程之間
- 程序順序規(guī)則:一個線程中的每個操作佑淀,happens- before 于該線程中的任意后續(xù)操作
- 監(jiān)視器鎖規(guī)則:對一個監(jiān)視器鎖的解鎖,happens- before 于隨后對這個監(jiān)視器鎖的加鎖
- volatile變量規(guī)則:對一個volatile域的寫彰檬,happens- before 于任意后續(xù)對這個volatile域的讀
- 傳遞性:如果A happens- before B伸刃,且B happens- before C,那么A happens- before C
注意逢倍,兩個操作之間具有happens-before關系捧颅,并不意味著前一個操作必須要在后一個操作之前執(zhí)行!happens-before僅僅要求前一個操作(執(zhí)行的結果)對后一個操作可見较雕,且前一個操作按順序排在第二個操作之前(the first is visible to and ordered before the second)
并發(fā)層次
通過Volatile機制和CAS指令實現(xiàn)了多核的
緩存一致性
通過MESI協(xié)議實現(xiàn)多核間的緩存一致性
CPU所有的操作都是在其各自核的緩存內(nèi)完成的碉哑,如果不回寫挚币,其他核根本不知道變量被修改了,只有當CPU對緩存進行回寫并通過MESI協(xié)議通知時才知道
Volatile
輕量級的同步機制扣典,不會引起線程的上下文切換
當寫一個volatile變量時妆毕,JMM會把該線程對應的本地內(nèi)存中的共享變量刷新到主內(nèi)存
當讀一個volatile變量時,JMM會把該線程對應的本地內(nèi)存置為無效贮尖。線程接下來將從主內(nèi)存中讀取共享變量笛粘。即每次都從主存中讀取最新的數(shù)據(jù)
但Volatile只能保證可見性,不能保證原子性
CAS
保證多核下指令執(zhí)行的原子性湿硝,但操作的是主存薪前,直接繞過了緩存(不會觸發(fā)緩存回寫)
所以要實現(xiàn)多核間的可見性,CAS操作的變量要使用volatile修飾
原子變量實現(xiàn)
利用volatile和cas
以AtomicInteger的實現(xiàn)為例
private volatile int value;
public final int incrementAndGet() {
// 一直循環(huán)图柏,直到更新成功為止
for (;;) {
// 獲取當前值
int current = get();
int next = current + 1;
// 使用cas原子性更新
if (compareAndSet(current, next))
return next;
}
}
// volatile修飾序六,每次都讀取最新的值
public final int get() {
return value;
}
AQS實現(xiàn)
本質(zhì)就是狀態(tài)+等待鏈表
通過一個內(nèi)部的Int變量來代表狀態(tài)任连,狀態(tài)的值代表通過還是等待
如果等待蚤吹,則提供一個非阻塞的等待鏈表(通過CAS來實現(xiàn)節(jié)點的增刪),同時在釋放時随抠,喚醒等待的線程
AQS是一個基礎類裁着,所有需要阻塞等待的,都可以使用其特性來實現(xiàn)
鎖實現(xiàn)
繼承于AQS拱她,對于互斥鎖二驰,狀態(tài)0為則沒有通過,并設置為1秉沼,否則阻塞等待
public void lock() {
sync.lock();
}
// NonFair為例
final void lock() {
// 設置狀態(tài)成功(0變成1)桶雀,成功獲取鎖
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// 再嘗試申請,失敗則等待
acquire(1);
}
// AQS
public final void acquire(int arg) {
// 如果嘗試申請失敗唬复,則將當前線程加入到等待隊列中
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 再次嘗試申請一次
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 如果是當前線程獲取的鎖矗积,則直接增加次數(shù)即可
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
并發(fā)數(shù)據(jù)結構
這里不對每個數(shù)據(jù)結構的實現(xiàn)方式做詳細描述,只簡單地列一下每個數(shù)據(jù)結構的大概使用方式
數(shù)據(jù)結構 | 說明 |
---|---|
SynchronousQueue | 并不是一個隊列敞咧,可以看成size=1的隊列棘捣,一次只能放入一個元素 |
PriorityBlockingQueue | 優(yōu)先級隊列 |
LinkedBlockingQueue | 鏈表隊列,隊列為空時需要等待休建,適合生產(chǎn)者-消費者模型 |
LinkedBlockingDeque | 鏈表的雙端隊列 |
DelayQueue | 延遲隊列 |
CopyOnWriteArraySet | 修改時進行復制的集合 |
CopyOnWriteArrayList | 修改時進行復制的隊列 |
ConcurrentSkipListSet | 有序的Set |
ConcurrentSkipListMap | 有序的Map |
ConcurrentLinkedQueue | 并發(fā)隊列乍恐,添加、獲取都不會等待 |
ConcurrentHashMap | 并發(fā)HashMap |
ArrayBlockingQueue | 數(shù)組隊列测砂,有界 |
參考
深入理解Java內(nèi)存模型(一)——基礎
深入理解Java內(nèi)存模型(四)——volatile
聊聊并發(fā)(一)——深入分析Volatile的實現(xiàn)原理
深入理解Java內(nèi)存模型(五)——鎖