Java的volatile和sychronized底層實現(xiàn)原理解析
1. 概覽
從Java代碼級別到硬件級別各層都是如何實現(xiàn)的

2. Synchronized
2.1 字節(jié)碼層面
使用javap -verbose <class文件>可以查看到字節(jié)碼信息,其中synchronized方法會有flags:ACC_SYNCHRONIZED,此時字節(jié)碼中不會包含monitorenter和moniotrexit,JVM會自動加
public synchronized void syncMethod(); flags: ACC_PUBLIC, ACC_SYNCHRONIZED
使用``javap -verbose <class文件>`編譯一個帶synchronized塊的代碼可以看到字節(jié)碼中的monitorenter和moniotrexit
0: new #2 // 創(chuàng)建一個新的Object實例 3: dup 4: invokespecial #1 // 調(diào)用Object的構(gòu)造函數(shù) 7: astore_1 // 將引用存儲到局部變量1(lock) 8: aload_1 // 將局部變量1(lock)加載到操作數(shù)棧 9: monitorenter // 進入monitor 10: ... // 同步塊體的字節(jié)碼 : aload_1 : monitorexit // 退出monitor : ...
2.2 JVM層面
源碼可以在Github上面查看
monitorenter底層是由JVM的代碼ObjectMonitor來實現(xiàn)的
ObjectMonitor() {
// 多線程競爭鎖進入時的單向鏈表
ObjectWaiter * volatile _cxq;
//處于等待鎖block狀態(tài)的線程,會被加入到該列表
ObjectWaiter * volatile _EntryList;
// _header是一個markOop類型,markOop就是對象頭中的Mark Word
volatile markOop _header;
// 搶占該鎖的線程數(shù),約等于WaitSet.size + EntryList.size
volatile intptr_t _count;
// 等待線程數(shù)
volatile intptr_t _waiters;
// 鎖的重入次數(shù)
volatile intptr_ _recursions;
// 監(jiān)視器鎖寄生的對象,鎖是寄托存儲于對象中
void* volatile _object;
// 指向持有ObjectMonitor對象的線程
void* volatile _owner;
// 處于wait狀態(tài)的線程,會被加入到_WaitSet
ObjectWaiter * volatile _WaitSet;
// 操作WaitSet鏈表的鎖
volatile int _WaitSetLock;
// 嵌套加鎖次數(shù),最外層鎖的_recursions屬性為0
volatile intptr_t _recursions;
}2.2.1 enter方法
整個方法比較長,但我們了解的無鎖、偏向鎖、輕量級鎖、重量級鎖都可以看到,核心方法是Atomic::cmpxchg_ptr,這個是CAS操作
| 鎖 | 方法 | 描述 |
|---|---|---|
| 偏向鎖 | Atomic::cmpxchg_ptr | 將owner替換為當前線程,成功則獲取到鎖 |
| 輕量級鎖 | TrySpin->Atomic::cmpxchg_ptr | 不斷自旋將owner替換為當前線程,成功則獲取到鎖 |
| 重量級鎖 | EnterI>Atomic::cmpxchg_ptr | park然后將owner替換為當前線程,成功則獲取到鎖 |
void ATTR ObjectMonitor::enter(TRAPS) {
// The following code is ordered to check the most common cases first
// and to reduce RTS->RTO cache line upgrades on SPARC and IA32 processors.
Thread * const Self = THREAD ;
void * cur ;
// 無鎖CAS 轉(zhuǎn)為 偏向鎖
cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
if (cur == NULL) {
// Either ASSERT _recursions == 0 or explicitly set _recursions = 0.
assert (_recursions == 0 , "invariant") ;
assert (_owner == Self, "invariant") ;
// CONSIDER: set or assert OwnerIsThread == 1
return ;
}
// 可重入鎖
if (cur == Self) {
// TODO-FIXME: check for integer overflow! BUGID 6557169.
_recursions ++ ;
return ;
}
if (Self->is_lock_owned ((address)cur)) {
assert (_recursions == 0, "internal state error");
_recursions = 1 ;
// Commute owner from a thread-specific on-stack BasicLockObject address to
// a full-fledged "Thread *".
_owner = Self ;
OwnerIsThread = 1 ;
return ;
}
// We've encountered genuine contention.
assert (Self->_Stalled == 0, "invariant") ;
Self->_Stalled = intptr_t(this) ;
// Try one round of spinning *before* enqueueing Self
// and before going through the awkward and expensive state
// transitions. The following spin is strictly optional ...
// Note that if we acquire the monitor from an initial spin
// we forgo posting JVMTI events and firing DTRACE probes.
// 自旋獲取鎖
if (Knob_SpinEarly && TrySpin (Self) > 0) {
assert (_owner == Self , "invariant") ;
assert (_recursions == 0 , "invariant") ;
assert (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ;
Self->_Stalled = 0 ;
return ;
}
assert (_owner != Self , "invariant") ;
assert (_succ != Self , "invariant") ;
assert (Self->is_Java_thread() , "invariant") ;
JavaThread * jt = (JavaThread *) Self ;
assert (!SafepointSynchronize::is_at_safepoint(), "invariant") ;
assert (jt->thread_state() != _thread_blocked , "invariant") ;
assert (this->object() != NULL , "invariant") ;
assert (_count >= 0, "invariant") ;
// Prevent deflation at STW-time. See deflate_idle_monitors() and is_busy().
// Ensure the object-monitor relationship remains stable while there's contention.
Atomic::inc_ptr(&_count);
EventJavaMonitorEnter event;
{ // Change java thread status to indicate blocked on monitor enter.
JavaThreadBlockedOnMonitorEnterState jtbmes(jt, this);
DTRACE_MONITOR_PROBE(contended__enter, this, object(), jt);
if (JvmtiExport::should_post_monitor_contended_enter()) {
JvmtiExport::post_monitor_contended_enter(jt, this);
}
OSThreadContendState osts(Self->osthread());
ThreadBlockInVM tbivm(jt);
Self->set_current_pending_monitor(this);
// TODO-FIXME: change the following for(;;) loop to straight-line code.
for (;;) {
jt->set_suspend_equivalent();
// cleared by handle_special_suspend_equivalent_condition()
// or java_suspend_self()
// 重量級鎖
EnterI (THREAD) ;
省略.......
}2.2.2 cmpxchg_ptr
上面的鎖都用了這個方法cmpxchg_ptr,這個和java中的cas是類似的,那它又是怎么實現(xiàn)的呢
其中cmpxchg是Linux操作系統(tǒng)的函數(shù),執(zhí)行了一段匯編指令,并且有l(wèi)ock前綴
// 多核心多cpu前面就要加lock
#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "
inline intptr_t Atomic::cmpxchg_ptr(intptr_t exchange_value, volatile intptr_t* dest, intptr_t compare_value) {
return (intptr_t)cmpxchg((jlong)exchange_value, (volatile jlong*)dest, (jlong)compare_value);
}
inline jlong Atomic::cmpxchg (jlong exchange_value, volatile jlong* dest, jlong compare_value) {
bool mp = os::is_MP();
__asm__ __volatile__ (LOCK_IF_MP(%4) "cmpxchgq %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}3. Volatile
3.1 字節(jié)碼層面
static volatile int greaterThanSevenCnt;
descriptor: I
flags: ACC_STATIC, ACC_VOLATILE3.2 JVM層面
可以看到判斷是否是volatile字段,是的話最后會有OrderAccess::storeload(); , 就是就是storeload屏障
CASE(_putfield):
CASE(_putstatic):
{
// .... 省略若干行
// ....
// Now store the result 現(xiàn)在要開始存儲結(jié)果了
// ConstantPoolCacheEntry* cache; -- cache是常量池緩存實例
// cache->is_volatile() -- 判斷是否有volatile訪問標志修飾
int field_offset = cache->f2_as_index();
if (cache->is_volatile()) { // ****重點判斷邏輯****
// volatile變量的賦值邏輯
if (tos_type == itos) {
obj->release_int_field_put(field_offset, STACK_INT(-1));
} else if (tos_type == atos) {// 對象類型賦值
VERIFY_OOP(STACK_OBJECT(-1));
obj->release_obj_field_put(field_offset, STACK_OBJECT(-1));
OrderAccess::release_store(&BYTE_MAP_BASE[(uintptr_t)obj >> CardTableModRefBS::card_shift], 0);
} else if (tos_type == btos) {// byte類型賦值
obj->release_byte_field_put(field_offset, STACK_INT(-1));
} else if (tos_type == ltos) {// long類型賦值
obj->release_long_field_put(field_offset, STACK_LONG(-1));
} else if (tos_type == ctos) {// char類型賦值
obj->release_char_field_put(field_offset, STACK_INT(-1));
} else if (tos_type == stos) {// short類型賦值
obj->release_short_field_put(field_offset, STACK_INT(-1));
} else if (tos_type == ftos) {// float類型賦值
obj->release_float_field_put(field_offset, STACK_FLOAT(-1));
} else {// double類型賦值
obj->release_double_field_put(field_offset, STACK_DOUBLE(-1));
}
// *** 寫完值后的storeload屏障 ***
OrderAccess::storeload();
} else {
// 非volatile變量的賦值邏輯
if (tos_type == itos) {
obj->int_field_put(field_offset, STACK_INT(-1));
} else if (tos_type == atos) {
VERIFY_OOP(STACK_OBJECT(-1));
obj->obj_field_put(field_offset, STACK_OBJECT(-1));
OrderAccess::release_store(&BYTE_MAP_BASE[(uintptr_t)obj >> CardTableModRefBS::card_shift], 0);
} else if (tos_type == btos) {
obj->byte_field_put(field_offset, STACK_INT(-1));
} else if (tos_type == ltos) {
obj->long_field_put(field_offset, STACK_LONG(-1));
} else if (tos_type == ctos) {
obj->char_field_put(field_offset, STACK_INT(-1));
} else if (tos_type == stos) {
obj->short_field_put(field_offset, STACK_INT(-1));
} else if (tos_type == ftos) {
obj->float_field_put(field_offset, STACK_FLOAT(-1));
} else {
obj->double_field_put(field_offset, STACK_DOUBLE(-1));
}
}
UPDATE_PC_AND_TOS_AND_CONTINUE(3, count);
}進入OrderAccess源碼可以看到,直接執(zhí)行了一段匯編指令,并且有l(wèi)ock前綴
inline void OrderAccess::storeload() { fence(); }
inline void OrderAccess::fence() {
if (os::is_MP()) {
// always use locked addl since mfence is sometimes expensive
#ifdef AMD64
__asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
#else
__asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
#endif
}
}4. lock指令
在上面的分析中,最底層都設(shè)計到匯編層面的lock指令,這個指令有什么作用呢?
根據(jù)匯編參考文檔IA-32 Assembly Language Reference Manual
The LOCK # signal is asserted during execution of the instruction following the lock prefix. This signal can be used in a multiprocessor system to ensure exclusive use of shared memory while LOCK # is asserted. The bts instruction is the read-modify-write sequence used to implement test-and-run. The lock prefix works only with the instructions listed here. If a lock prefix is used with any other instructions, an undefined opcode trap is generated.
Lock是一個指令前綴,用于多核處理器系統(tǒng)不使用共享內(nèi)存
那么它又是怎么讓其他核心不訪問共享內(nèi)存,有兩種方法
- 鎖內(nèi)存總線,也就是說執(zhí)行這條指令的時候,其他的核心都不能在訪問內(nèi)存了
- 鎖緩存行,現(xiàn)在CPU本身是有多級緩存的,而這些緩存是如何保持一致的,由MESI來支持,MESI協(xié)議可以保證其他核心不使用內(nèi)存,或者換一種說法,可以使用,但被修改的內(nèi)容會失效
5. MESI協(xié)議
現(xiàn)代CPU多核架構(gòu)中為了協(xié)調(diào)快速的CPU運算和相對較慢的內(nèi)存讀寫速度之間的矛盾,在CPU和內(nèi)存之間引入了CPU cache:

MESI協(xié)議下,緩存行(cache line)有四種狀態(tài)來保證緩存的一致性
- 已修改Modified (M) 緩存行是臟的,與主存的值不同。如果別的CPU內(nèi)核要讀主存這塊數(shù)據(jù),該緩存行必須回寫到主存,狀態(tài)變?yōu)楣蚕?S)
- 獨占Exclusive (E) 緩存行只在當前緩存中,但是干凈的(clean)–緩存數(shù)據(jù)同于主存數(shù)據(jù)。當別的緩存讀取它時,狀態(tài)變?yōu)楣蚕恚划斍皩憯?shù)據(jù)時,變?yōu)橐研薷臓顟B(tài)。
- 共享Shared (S) 緩存行也存在于其它緩存中且是干凈的。緩存行可以在任意時刻拋棄。
- 無效Invalid (I) 緩存行是無效的,需要從主內(nèi)存中讀取最新值
每次要修改緩存,如果緩存行狀態(tài)為 S 的話都要先發(fā)一個 invalidate 的廣播,再等其他 CPU 將緩存行設(shè)置為無效后返回 invalidate ack 才能寫到 Cache 中,因為這樣才能保證緩存的一致性
但是如果 CPU 頻繁地修改數(shù)據(jù),就會不斷地發(fā)送廣播消息,CPU 只能被動同步地等待其他 CPU 的消息,顯然會對執(zhí)行效率產(chǎn)生影響
為了解決此問題,工程師在 CPU 和 cache 之間又加了一個 store buffer,同時在cache和總線之間添加了Invalidate Queue
這個buffer可以讓廣播和收廣播的處理異步化,效率當然會變高,但強一致性變?yōu)榱俗罱K一致性
lock指令是CPU硬件工程師給程序員留的一個口子,把對MESI協(xié)議的優(yōu)化(store buffer, invalidate queue)禁用,暫時以同步方式工作,使得對于該關(guān)鍵字的MESI協(xié)議退回強一致性狀態(tài)
6. 總結(jié)
分析到此:
所有的并發(fā)問題可以概括為,多個核心同時修改內(nèi)存數(shù)據(jù),導(dǎo)致結(jié)果不符合預(yù)期
解決并發(fā)問題的方法可以概括為,同一時間只能讓一個核心修改內(nèi)存,但有多種手段,例如鎖總線、或者廣播讓其他核心失效
7. 其他問題
既然sychronized的和volatile底層實現(xiàn)是一樣的,那么volatile為什么沒有原子性呢?
在于鎖定的范圍,volatile修飾的是一個字段,只能保證讀和寫是原子性的,但讀出來、在計算、寫入分為三步則不是原子性的。
sychronized底層也用了volatile的,但它的鎖定范圍是程序員指定的,這個范圍之間的代碼是原子的
cas volatile變量開始鎖定 任意程序代碼 cas volatile變量釋放鎖定
現(xiàn)在一般推薦使用Java的Atomic類,他是通過CAS來實現(xiàn)的,它和sychronized的區(qū)別是什么?
cas不能單獨使用,需要加自旋操作,本身是一個樂觀鎖
sychronized本身結(jié)合了樂觀鎖和悲觀鎖,悲觀鎖會讓線程park然后重試,不會消耗CPU,而樂觀鎖但不斷消耗cpu
8. 對比
在閱讀ObjectMonitor代碼時,發(fā)現(xiàn)有很熟悉的感覺

發(fā)現(xiàn)這些鎖的數(shù)據(jù)結(jié)果都是類似的,一個volatile變量加一個等待隊列
參考
【1】]synchronized 關(guān)鍵字底層原理
【2】Java多線程:objectMonitor源碼解讀(3)
【3】Linux Kernel CMPXCHG函數(shù)分析
【4】聊聊CPU的LOCK指令
【5】12 張圖看懂 CPU 緩存一致性與 MESI 協(xié)議,真的一致嗎?
【9】CAS你以為你真的懂?
【10】x86 LOCK 指令前綴
【11】Linux Mutex機制分析
到此這篇關(guān)于Java的volatile和sychronized底層實現(xiàn)原理解析的文章就介紹到這了,更多相關(guān)Java volatile和sychronized底層內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java經(jīng)典設(shè)計模式之觀察者模式原理與用法詳解
這篇文章主要介紹了Java經(jīng)典設(shè)計模式之觀察者模式,簡單分析了觀察者模式的概念、原理并結(jié)合實例形式給出了java觀察者模式的具體用法與相關(guān)注意事項,需要的朋友可以參考下2017-08-08
Java畢業(yè)設(shè)計之多用戶宿舍管理系統(tǒng)的實現(xiàn)
這篇文章主要介紹了基于Java實現(xiàn)的多用戶宿舍管理系統(tǒng),本文采用了jsp、servlet、jdbc等技術(shù),文中示例代碼講解詳細,需要的可以參考一下2022-02-02
Java 數(shù)據(jù)結(jié)構(gòu)與算法系列精講之紅黑樹
紅黑樹的應(yīng)用比較廣泛,主要是用它來存儲有序的數(shù)據(jù),它的時間復(fù)雜度是O(lgn),效率非常之高。例如,Java集合中的TreeSet和TreeMap,C++ STL中的set、map,以及Linux虛擬內(nèi)存的管理,都是通過紅黑樹去實現(xiàn)的2022-02-02
java 如何往已經(jīng)存在的excel表格里面追加數(shù)據(jù)的方法
這篇文章主要介紹了java 如何往已經(jīng)存在的excel表格里面追加數(shù)據(jù)的方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
Spring Boot 自定義 Shiro 過濾器無法使用 @Autowired問題及解決方法
這篇文章主要介紹了Spring Boot 自定義 Shiro 過濾器無法使用 @Autowired問題及解決方法 ,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-06-06

