這篇文章將為大家詳細(xì)講解有關(guān)如何在Java中實(shí)現(xiàn)鎖,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。
成都創(chuàng)新互聯(lián)公司成立與2013年,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢(mèng)想脫穎而出為使命,1280元和平做網(wǎng)站,已為上家服務(wù),為和平各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話:18980820575
1. 概述
鎖是Java并發(fā)編程中最重要的同步機(jī)制。鎖除了讓臨界區(qū)互斥執(zhí)行外,還可以讓釋放鎖的線程獲取同一個(gè)鎖的線程發(fā)送消息。
2. 鎖的內(nèi)存語義
當(dāng)線程獲取鎖時(shí), JMM會(huì)把線程對(duì)應(yīng)的本地內(nèi)存置為無效. 從而使得被監(jiān)視器保護(hù)的臨界區(qū)的變量必須從主內(nèi)存中讀取.
當(dāng)線程釋放鎖時(shí), JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存中的共享變量刷新到主內(nèi)存中(并不是不釋放鎖就不刷新到主內(nèi)存, 只是釋放鎖時(shí)把未刷新到主內(nèi)存中的數(shù)據(jù)刷新到主內(nèi)存).
鎖的內(nèi)存語義與volatile的內(nèi)存語義
鎖獲取與volatile讀有相同的內(nèi)存語義.
鎖釋放與volatile寫有相同的內(nèi)存語義.
內(nèi)存語義總結(jié)
線程A釋放一個(gè)鎖, 實(shí)質(zhì)上是線程A向接下來將要獲取這個(gè)鎖的某個(gè)線程發(fā)出了(線程A對(duì)共享變量所做修改的)消息.
線程B獲取一個(gè)鎖, 實(shí)質(zhì)上是線程B接收了之前某個(gè)線程發(fā)出的(在釋放這個(gè)鎖之前對(duì)共享變量所做修改的)消息.
線程A釋放鎖, 隨后線程B獲取這個(gè)鎖, 這個(gè)過程實(shí)質(zhì)上是線程A通過主內(nèi)存向線程B發(fā)送消息.
3. 鎖內(nèi)存語義的實(shí)現(xiàn)
下面以ReentrantLock為例, 獲取到鎖就是把state改為1(不考慮重入), 釋放鎖時(shí)改為0.
而加鎖的關(guān)鍵代碼就是
protected final boolean compareAndSetState(int expect, int update) { return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }
該方法以原子操作的方式更新state變量, 本文把Java的compareAndSet()方法簡(jiǎn)稱為CAS. JDK文檔對(duì)該方法的說明如下: 如果當(dāng)前狀態(tài)值等于預(yù)期值, 則以原子方式將同步狀態(tài)設(shè)置為給定的更新值. 此操作具有volatile讀和寫的內(nèi)存語義.
這里我們分別從編譯器和處理器的角度來分析: CAS如何同時(shí)具有volatile讀和volatile寫的內(nèi)存語義.
我們知道, 編譯器不會(huì)對(duì)volatile讀與volatile讀后面的任意內(nèi)存操作重排序; 編譯器不會(huì)對(duì)volatile寫與volatile寫前面的任意內(nèi)存操作重排序. 組合這兩個(gè)條件, 意味著為了同時(shí)實(shí)現(xiàn)volatile讀和volatile寫的內(nèi)存語義, 編譯器不能對(duì)CAS與CAS前面和后面的任意內(nèi)存操作重排序.
下面我們來分析在常見的intel X86處理器中, CAS是如何同時(shí)具有volatile讀和volatile寫的內(nèi)存語義的.
下面是sun.misc.Unsafe類的compareAndSwapInt()方法的源代碼.
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
可以看到, 這是一個(gè)本地方法調(diào)用. 這個(gè)本地方法在openjdk中依次調(diào)用的c++代碼為: unsafe.cpp, atomic.cpp 和 atomic_windows_x86.inline.hpp. 這個(gè)本地方法的最終實(shí)現(xiàn)在openjdk的如下位置: openjdk-7-fcs-src-b147-
27_jun_2011\openjdk\hotspot\src\os_cpu\windows_x86\vm\atomic_windows_x86.inline.hpp(對(duì)應(yīng)于
Windows操作系統(tǒng), X86處理器). 下面是對(duì)應(yīng)于intel X86處理器的源代碼的片段.
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) { // alternative for InterlockedCompareExchange int mp = os::is_MP(); __asm { mov edx, dest mov ecx, exchange_value mov eax, compare_value LOCK_IF_MP(mp) cmpxchg dword ptr [edx], ecx } }
如上面源代碼所示, 程序會(huì)根據(jù)當(dāng)前處理器的類型來決定是否為cmpxchg指令添加lock前綴. 如果程序是在多處理器上運(yùn)行, 就為cmpxchg指令加上lock前綴(Lock Cmpxchg). 反之, 如果程序是在單處理器上運(yùn)行, 就省略lock前綴(單處理器自身會(huì)維護(hù)單處理器內(nèi)的順序一致性, 不需要lock前綴提供的內(nèi)存屏障效果).
intel的手冊(cè)對(duì)lock前綴的說明如下.
確保對(duì)內(nèi)存的讀-改-寫操作原子執(zhí)行. 在Pentium及Pentium之前的處理器中, 帶有l(wèi)ock前綴的指令在執(zhí)行期間會(huì)鎖住總線, 使得其他處理器暫時(shí)無法通過總線訪問內(nèi)存. 很顯然, 這會(huì)帶來昂貴的開銷. 從Pentium 4、Intel Xeon及P6處理器開始, Intel使用緩存鎖定(Cache Locking)
來保證指令執(zhí)行的原子性. 緩存鎖定將大大降低lock前綴指令的執(zhí)行開銷.
禁止該指令, 與之前和之后的讀和寫指令重排序.
把寫緩沖區(qū)中的所有數(shù)據(jù)刷新到內(nèi)存中.
關(guān)于如何在Java中實(shí)現(xiàn)鎖就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。