JAVA ReentrantLock可見性?

正如官網上所說的ReentrantLock與sync的語義是一致的,也就是說ReentrantLock也具有一致性、可見性。通過源碼可知,在最極端的情況下ReentrantLock的lock、unlock只會對volatile 修飾的 state 變數操作,並不會涉及到其他太多的native代碼。

我想問的是:ReentrantLock 是如何保證臨界區代碼的可見性的?

static int x = 0;

ReentrantLock _LOCK = new ReentrantLock();

public static void inc(){

_LOCK.lock();

x++;

_LOCK.unlock();

}

這個靜態變數x是如何對其他線程可見的?

賜教。


volatile修飾的state保證了lock和unlock的Happens-Before的偏序關係,根據volatile規則:volatile變數的寫入必須在讀取之前執行,保證了可重入鎖ReentrantLock的unlock方法必須在lock方法返回前執行。

關於可見性,參照lock和unlock的源碼,可以追溯到與之相對應的Parker::park和Parker::unpark函數的實現:(貼出HotSpotVM bsd平台的實現版本)

void Parker::park(bool isAbsolute, jlong time) {
// Ideally we"d do something useful while spinning, such
// as calling unpackTime().

// Optional fast-path check:
// Return immediately if a permit is available.
// We depend on Atomic::xchg() having full barrier semantics
// since we are doing a lock-free update to _counter.
if (Atomic::xchg(0, _counter) &> 0) return;

Thread* thread = Thread::current();
assert(thread-&>is_Java_thread(), "Must be JavaThread");
JavaThread *jt = (JavaThread *)thread;

// Optional optimization -- avoid state transitions if there"s an interrupt pending.
// Check interrupt before trying to wait
if (Thread::is_interrupted(thread, false)) {
return;
}

// Next, demultiplex/decode time arguments
struct timespec absTime;
if (time &< 0 || (isAbsolute time == 0) ) { // don"t wait at all return; } if (time &> 0) {
unpackTime(absTime, isAbsolute, time);
}

// Enter safepoint region
// Beware of deadlocks such as 6317397.
// The per-thread Parker:: mutex is a classic leaf-lock.
// In particular a thread must never block on the Threads_lock while
// holding the Parker:: mutex. If safepoints are pending both the
// the ThreadBlockInVM() CTOR and DTOR may grab Threads_lock.
ThreadBlockInVM tbivm(jt);

// Don"t wait if cannot get lock since interference arises from
// unblocking. Also. check interrupt before trying wait
if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) {
return;
}

int status ;
if (_counter &> 0) { // no wait needed
_counter = 0;
status = pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant") ;
// Paranoia to ensure our locked and lock-free paths interact
// correctly with each other and Java-level accesses.
OrderAccess::fence();
return;
}

#ifdef ASSERT
// Don"t catch signals while blocked; let the running threads have the signals.
// (This allows a debugger to break into the running thread.)
sigset_t oldsigs;
sigset_t* allowdebug_blocked = os::Bsd::allowdebug_blocked_signals();
pthread_sigmask(SIG_BLOCK, allowdebug_blocked, oldsigs);
#endif

OSThreadWaitState osts(thread-&>osthread(), false /* not Object.wait() */);
jt-&>set_suspend_equivalent();
// cleared by handle_special_suspend_equivalent_condition() or java_suspend_self()

if (time == 0) {
status = pthread_cond_wait (_cond, _mutex) ;
} else {
status = os::Bsd::safe_cond_timedwait (_cond, _mutex, absTime) ;
if (status != 0 WorkAroundNPTLTimedWaitHang) {
pthread_cond_destroy (_cond) ;
pthread_cond_init (_cond, NULL);
}
}
assert_status(status == 0 || status == EINTR ||
status == ETIMEDOUT,
status, "cond_timedwait");

#ifdef ASSERT
pthread_sigmask(SIG_SETMASK, oldsigs, NULL);
#endif

_counter = 0 ;
status = pthread_mutex_unlock(_mutex) ;
assert_status(status == 0, status, "invariant") ;
// Paranoia to ensure our locked and lock-free paths interact
// correctly with each other and Java-level accesses.
OrderAccess::fence();

// If externally suspended while waiting, re-suspend
if (jt-&>handle_special_suspend_equivalent_condition()) {
jt-&>java_suspend_self();
}
}

void Parker::unpark() {
int s, status ;
status = pthread_mutex_lock(_mutex);
assert (status == 0, "invariant") ;
s = _counter;
_counter = 1;
if (s &< 1) { if (WorkAroundNPTLTimedWaitHang) { status = pthread_cond_signal (_cond) ; assert (status == 0, "invariant") ; status = pthread_mutex_unlock(_mutex); assert (status == 0, "invariant") ; } else { status = pthread_mutex_unlock(_mutex); assert (status == 0, "invariant") ; status = pthread_cond_signal (_cond) ; assert (status == 0, "invariant") ; } } else { pthread_mutex_unlock(_mutex); assert (status == 0, "invariant") ; }

可以看到unpark函數調用後,通過pthread_cond_signal喚醒被阻塞線程,被阻塞線程獲得_mutex鎖後執行pthread_cond_wait後的代碼,在pthread_mutex_unlock之後,顯示地調用了OrderAccess::fence()這個函數;這個函數是HotSpot VM對JMM的內存屏障一個具體的實現函數; 我們來看看它的實現,在此還是貼出bsd x86的實現版本:

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
}
}

這裡針對多處理器(MP)平台,採用內嵌彙編的實現,使用volatile阻止編譯器優化,最後使用lock; addl $0,0(%%rsp)的方式代替mfence指令實現內存屏障的功能,原因注釋寫得很清楚,mfence指令有時過於昂貴。

HotSpot VM 對於volatile的field進行put_field和get_field時,也會顯示地調用OrderAccess的MOAM的函數實現,例如:put_field指令,就調用了OrderAccess::storeload()。

————————————————————

在此給出自己的一知半解,歡迎大家繼續深挖!

並發編程網 - ifeve.com


可見性不是指對一個變數修改後,其它線程馬上能看到這個值,而是一個相對的概念。

舉個例子,假定兩個變數A,B,初始值都是0;

線程1:

A = 1;(1)
B = 1;(2)

線程2:

if (B == 1) (3)
A = ? (4)

此處A的值可能是1,也可能是0,由於CPU的亂序執行(不同的CPU架構有不同的亂序模型)。

如果將B定義為volatile呢?此處A的值只能是1。因為JMM的volatile語義限制了CPU的亂序行為,volatile寫限制之前的代碼跑到volatile寫之前執行,volatile讀限制之後的代碼在volatile讀之前執行。這裡語句3確保發生在語句2之後,推斷出語句4發生在語句1之後,也就是happens-before關係。lock也有相同的語義,語句2相當於unlock,語句3相當於lock。

所以JMM要求我們對存在數據競爭的變數訪問用同步元語(volatile,lock等)進行同步,同步限制了亂序執行,也就是確保了可見性。


可以看下這篇文章《Java鎖是如何保證數據可見性的》:並發編程網 - ifeve.com


樓主問的是可見性,樓上們別瞎答,簡述吧,寫太多樓主該暈了

用volatile的happenbefore性質百度可查

lock和unlock方法中有對volatile變數的讀取和寫入,所以你的x就被happenbefore包圍了,也就能被其他縣城讀到


推薦閱讀:

為什麼有人想要成為程序員?
金融女,怎樣能認識到聰明有趣的程序員?
寫Python的時候總是用別人的庫,這樣對提高代碼能力有幫助嗎?
碼農生活規律嗎?
為什麼公開的一些代碼的注釋真的好少,沒有注釋我們這些新手好難玩?

TAG:程序員 | Java | Java虛擬機JVM | 編譯原理 |