Java并發(fā)指南4:Java中的鎖 Lock和synchronized

本文轉(zhuǎn)載自并發(fā)編程網(wǎng)楞陷,侵刪

本系列文章將整理到我在GitHub上的《Java面試指南》倉(cāng)庫(kù),更多精彩內(nèi)容請(qǐng)到我的倉(cāng)庫(kù)里查看

https://github.com/h2pl/Java-Tutorial

喜歡的話麻煩點(diǎn)下Star哈

文章同步發(fā)于我的個(gè)人博客:

www.how2playlife.com

本文是微信公眾號(hào)【Java技術(shù)江湖】的《Java并發(fā)指南》其中一篇诱担,本文大部分內(nèi)容來(lái)源于網(wǎng)絡(luò),為了把本文主題講得清晰透徹,也整合了很多我認(rèn)為不錯(cuò)的技術(shù)博客內(nèi)容,引用其中了一些比較好的博客文章凿跳,如有侵權(quán),請(qǐng)聯(lián)系作者疮方。

該系列博文會(huì)告訴你如何全面深入地學(xué)習(xí)Java并發(fā)技術(shù)控嗜,從Java多線程基礎(chǔ),再到并發(fā)編程的基礎(chǔ)知識(shí)案站,從Java并發(fā)包的入門和實(shí)戰(zhàn)躬审,再到JUC的源碼剖析,一步步地學(xué)習(xí)Java并發(fā)編程蟆盐,并上手進(jìn)行實(shí)戰(zhàn),以便讓你更完整地了解整個(gè)Java并發(fā)編程知識(shí)體系遭殉,形成自己的知識(shí)框架石挂。

為了更好地總結(jié)和檢驗(yàn)?zāi)愕膶W(xué)習(xí)成果,本系列文章也會(huì)提供一些對(duì)應(yīng)的面試題以及參考答案险污。

如果對(duì)本系列文章有什么建議痹愚,或者是有什么疑問(wèn)的話,也可以關(guān)注公眾號(hào)【Java技術(shù)江湖】聯(lián)系作者蛔糯,歡迎你參與本系列博文的創(chuàng)作和修訂拯腮。

Java中的鎖機(jī)制及Lock類

鎖的釋放-獲取建立的happens before 關(guān)系

鎖是java并發(fā)編程中最重要的同步機(jī)制。鎖除了讓臨界區(qū)互斥執(zhí)行外蚁飒,還可以讓釋放鎖的線程向獲取同一個(gè)鎖的線程發(fā)送消息动壤。

下面是鎖釋放-獲取的示例代碼:
class MonitorExample {
int a = 0;

    public synchronized void writer() {  //1
        a++;                             //2
    }                                    //3
 
    public synchronized void reader() {  //4
        int i = a;                       //5
        ……
    }                                    //6
}

根據(jù)程序次序規(guī)則,1 happens before 2, 2 happens before 3; 4 happens before 5, 5 happens before 6淮逻。假設(shè)線程A執(zhí)行writer()方法琼懊,隨后線程B執(zhí)行reader()方法阁簸。根據(jù)happens before規(guī)則,這個(gè)過(guò)程包含的happens before 關(guān)系可以分為兩類:

  1. 根據(jù)監(jiān)視器鎖規(guī)則哼丈,3 happens before 4启妹。
  2. 根據(jù)happens before 的傳遞性,2 happens before 5醉旦。

上述happens before 關(guān)系的圖形化表現(xiàn)形式如下:

在上圖中饶米,每一個(gè)箭頭鏈接的兩個(gè)節(jié)點(diǎn),代表了一個(gè)happens before 關(guān)系车胡。黑色箭頭表示程序順序規(guī)則檬输;橙色箭頭表示監(jiān)視器鎖規(guī)則;藍(lán)色箭頭表示組合這些規(guī)則后提供的happens before保證吨拍。

上圖表示在線程A釋放了鎖之后褪猛,隨后線程B獲取同一個(gè)鎖。在上圖中羹饰,2 happens before 5伊滋。因此,線程A在釋放鎖之前所有可見(jiàn)的共享變量队秩,在線程B獲取同一個(gè)鎖之后笑旺,將立刻變得對(duì)B線程可見(jiàn)。

鎖釋放和獲取的內(nèi)存語(yǔ)義

當(dāng)線程釋放鎖時(shí)馍资,JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存中的共享變量刷新到主內(nèi)存中筒主。以上面的MonitorExample程序?yàn)槔珹線程釋放鎖后鸟蟹,共享數(shù)據(jù)的狀態(tài)示意圖如下:

當(dāng)線程獲取鎖時(shí)乌妙,JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存置為無(wú)效。從而使得被監(jiān)視器保護(hù)的臨界區(qū)代碼必須要從主內(nèi)存中去讀取共享變量建钥。下面是鎖獲取的狀態(tài)示意圖:

對(duì)比鎖釋放-獲取的內(nèi)存語(yǔ)義與volatile寫-讀的內(nèi)存語(yǔ)義藤韵,可以看出:鎖釋放與volatile寫有相同的內(nèi)存語(yǔ)義;鎖獲取與volatile讀有相同的內(nèi)存語(yǔ)義熊经。

下面對(duì)鎖釋放和鎖獲取的內(nèi)存語(yǔ)義做個(gè)總結(jié):

  • 線程A釋放一個(gè)鎖泽艘,實(shí)質(zhì)上是線程A向接下來(lái)將要獲取這個(gè)鎖的某個(gè)線程發(fā)出了(線程A對(duì)共享變量所做修改的)消息。
  • 線程B獲取一個(gè)鎖镐依,實(shí)質(zhì)上是線程B接收了之前某個(gè)線程發(fā)出的(在釋放這個(gè)鎖之前對(duì)共享變量所做修改的)消息匹涮。
  • 線程A釋放鎖,隨后線程B獲取這個(gè)鎖槐壳,這個(gè)過(guò)程實(shí)質(zhì)上是線程A通過(guò)主內(nèi)存向線程B發(fā)送消息然低。

鎖內(nèi)存語(yǔ)義的實(shí)現(xiàn)

本文將借助ReentrantLock的源代碼,來(lái)分析鎖內(nèi)存語(yǔ)義的具體實(shí)現(xiàn)機(jī)制。

請(qǐng)看下面的示例代碼:

class ReentrantLockExample {
int a = 0;
ReentrantLock lock = new ReentrantLock();
 
public void writer() {
    lock.lock();         //獲取鎖
    try {
        a++;
    } finally {
        lock.unlock();  //釋放鎖
    }
}
 
public void reader () {
    lock.lock();        //獲取鎖
    try {
        int i = a;
        ……
    } finally {
        lock.unlock();  //釋放鎖
    }
}
}

在ReentrantLock中脚翘,調(diào)用lock()方法獲取鎖灼卢;調(diào)用unlock()方法釋放鎖。

ReentrantLock的實(shí)現(xiàn)依賴于java同步器框架AbstractQueuedSynchronizer(本文簡(jiǎn)稱之為AQS)来农。AQS使用一個(gè)整型的volatile變量(命名為state)來(lái)維護(hù)同步狀態(tài)鞋真,馬上我們會(huì)看到,這個(gè)volatile變量是ReentrantLock內(nèi)存語(yǔ)義實(shí)現(xiàn)的關(guān)鍵沃于。 下面是ReentrantLock的類圖(僅畫(huà)出與本文相關(guān)的部分):

ReentrantLock分為公平鎖和非公平鎖涩咖,我們首先分析公平鎖。

使用公平鎖時(shí)繁莹,加鎖方法lock()的方法調(diào)用軌跡如下:

  1. ReentrantLock : lock()
  2. FairSync : lock()
  3. AbstractQueuedSynchronizer : acquire(int arg)
  4. ReentrantLock : tryAcquire(int acquires)

在第4步真正開(kāi)始加鎖檩互,下面是該方法的源代碼:

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();   //獲取鎖的開(kāi)始,首先讀volatile變量state
    if (c == 0) {
        if (isFirst(current) &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)  
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

從上面源代碼中我們可以看出咨演,加鎖方法首先讀volatile變量state闸昨。

在使用公平鎖時(shí),解鎖方法unlock()的方法調(diào)用軌跡如下:

  1. ReentrantLock : unlock()
  2. AbstractQueuedSynchronizer : release(int arg)
  3. Sync : tryRelease(int releases)

在第3步真正開(kāi)始釋放鎖薄风,下面是該方法的源代碼:

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);           //釋放鎖的最后饵较,寫volatile變量state
    return free;
}

從上面的源代碼我們可以看出,在釋放鎖的最后寫volatile變量state遭赂。

公平鎖在釋放鎖的最后寫volatile變量state循诉;在獲取鎖時(shí)首先讀這個(gè)volatile變量。根據(jù)volatile的happens-before規(guī)則撇他,釋放鎖的線程在寫volatile變量之前可見(jiàn)的共享變量茄猫,在獲取鎖的線程讀取同一個(gè)volatile變量后將立即變的對(duì)獲取鎖的線程可見(jiàn)。

現(xiàn)在我們分析非公平鎖的內(nèi)存語(yǔ)義的實(shí)現(xiàn)困肩。

非公平鎖的釋放和公平鎖完全一樣划纽,所以這里僅僅分析非公平鎖的獲取。

使用公平鎖時(shí)锌畸,加鎖方法lock()的方法調(diào)用軌跡如下:

  1. ReentrantLock : lock()
  2. NonfairSync : lock()
  3. AbstractQueuedSynchronizer : compareAndSetState(int expect, int update)

在第3步真正開(kāi)始加鎖阿浓,下面是該方法的源代碼:

<pre name="code">protected final boolean compareAndSetState(int expect, int update) {return unsafe.compareAndSwapInt(this, stateOffset, expect, update);}</pre>

該方法以原子操作的方式更新state變量,本文把java的compareAndSet()方法調(diào)用簡(jiǎn)稱為CAS蹋绽。JDK文檔對(duì)該方法的說(shuō)明如下:如果當(dāng)前狀態(tài)值等于預(yù)期值,則以原子方式將同步狀態(tài)設(shè)置為給定的更新值筋蓖。此操作具有 volatile 讀和寫的內(nèi)存語(yǔ)義卸耘。

這里我們分別從編譯器和處理器的角度來(lái)分析,CAS如何同時(shí)具有volatile讀和volatile寫的內(nèi)存語(yǔ)義。

前文我們提到過(guò)粘咖,編譯器不會(huì)對(duì)volatile讀與volatile讀后面的任意內(nèi)存操作重排序蚣抗;編譯器不會(huì)對(duì)volatile寫與volatile寫前面的任意內(nèi)存操作重排序。組合這兩個(gè)條件,意味著為了同時(shí)實(shí)現(xiàn)volatile讀和volatile寫的內(nèi)存語(yǔ)義翰铡,編譯器不能對(duì)CAS與CAS前面和后面的任意內(nèi)存操作重排序钝域。

下面我們來(lái)分析在常見(jiàn)的intel x86處理器中,CAS是如何同時(shí)具有volatile讀和volatile寫的內(nèi)存語(yǔ)義的锭魔。

下面是sun.misc.Unsafe類的compareAndSwapInt()方法的源代碼:

protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

可以看到這是個(gè)本地方法調(diào)用例证。這個(gè)本地方法在openjdk中依次調(diào)用的c++代碼為:unsafe.cpp,atomic.cpp和atomicwindowsx86.inline.hpp迷捧。這個(gè)本地方法的最終實(shí)現(xiàn)在openjdk的如下位置:openjdk-7-fcs-src-b147-27jun2011\openjdk\hotspot\src\oscpu\windowsx86\vm\ atomicwindowsx86.inline.hpp(對(duì)應(yīng)于windows操作系統(tǒng)织咧,X86處理器)。下面是對(duì)應(yīng)于intel x86處理器的源代碼的片段:

// Adding a lock prefix to an instruction on MP machine
// VC++ doesn't like the lock prefix to be on a single line
// so we can't insert a label after the lock prefix.
// By emitting a lock prefix, we can define a label after it.
#define LOCK_IF_MP(mp) __asm cmp mp, 0  \
                       __asm je L0      \
                       __asm _emit 0xF0 \
                       __asm L0:
 
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)前處理器的類型來(lái)決定是否為cmpxchg指令添加lock前綴笙蒙。如果程序是在多處理器上運(yùn)行,就為cmpxchg指令加上lock前綴(lock cmpxchg)庆锦。反之捅位,如果程序是在單處理器上運(yùn)行,就省略lock前綴(單處理器自身會(huì)維護(hù)單處理器內(nèi)的順序一致性搂抒,不需要lock前綴提供的內(nèi)存屏障效果)艇搀。

intel的手冊(cè)對(duì)lock前綴的說(shuō)明如下:

  1. 確保對(duì)內(nèi)存的讀-改-寫操作原子執(zhí)行。在Pentium及Pentium之前的處理器中燕耿,帶有l(wèi)ock前綴的指令在執(zhí)行期間會(huì)鎖住總線中符,使得其他處理器暫時(shí)無(wú)法通過(guò)總線訪問(wèn)內(nèi)存。很顯然誉帅,這會(huì)帶來(lái)昂貴的開(kāi)銷淀散。

  2. 從Pentium 4,Intel Xeon及P6處理器開(kāi)始蚜锨,intel在原有總線鎖的基礎(chǔ)上做了一個(gè)很有意義的優(yōu)化:如果要訪問(wèn)的內(nèi)存區(qū)域(area of memory)在lock前綴指令執(zhí)行期間已經(jīng)在處理器內(nèi)部的緩存中被鎖定(即包含該內(nèi)存區(qū)域的緩存行當(dāng)前處于獨(dú)占或以修改狀態(tài))档插,并且該內(nèi)存區(qū)域被完全包含在單個(gè)緩存行(cache line)中,那么處理器將直接執(zhí)行該指令亚再。

  3. 由于在指令執(zhí)行期間該緩存行會(huì)一直被鎖定郭膛,其它處理器無(wú)法讀/寫該指令要訪問(wèn)的內(nèi)存區(qū)域,因此能保證指令執(zhí)行的原子性氛悬。這個(gè)操作過(guò)程叫做緩存鎖定(cache locking)则剃,緩存鎖定將大大降低lock前綴指令的執(zhí)行開(kāi)銷,但是當(dāng)多處理器之間的競(jìng)爭(zhēng)程度很高或者指令訪問(wèn)的內(nèi)存地址未對(duì)齊時(shí)如捅,仍然會(huì)鎖住總線棍现。

  4. 禁止該指令與之前和之后的讀和寫指令重排序。

  5. 把寫緩沖區(qū)中的所有數(shù)據(jù)刷新到內(nèi)存中镜遣。

上面的第2點(diǎn)和第3點(diǎn)所具有的內(nèi)存屏障效果己肮,足以同時(shí)實(shí)現(xiàn)volatile讀和volatile寫的內(nèi)存語(yǔ)義。

經(jīng)過(guò)上面的這些分析,現(xiàn)在我們終于能明白為什么JDK文檔說(shuō)CAS同時(shí)具有volatile讀和volatile寫的內(nèi)存語(yǔ)義了谎僻。

現(xiàn)在對(duì)公平鎖和非公平鎖的內(nèi)存語(yǔ)義做個(gè)總結(jié):

  • 公平鎖和非公平鎖釋放時(shí)娄柳,最后都要寫一個(gè)volatile變量state。
  • 公平鎖獲取時(shí)艘绍,首先會(huì)去讀這個(gè)volatile變量赤拒。
  • 非公平鎖獲取時(shí),首先會(huì)用CAS更新這個(gè)volatile變量,這個(gè)操作同時(shí)具有volatile讀和volatile寫的內(nèi)存語(yǔ)義鞍盗。

從本文對(duì)ReentrantLock的分析可以看出需了,鎖釋放-獲取的內(nèi)存語(yǔ)義的實(shí)現(xiàn)至少有下面兩種方式:

  1. 利用volatile變量的寫-讀所具有的內(nèi)存語(yǔ)義。
  2. 利用CAS所附帶的volatile讀和volatile寫的內(nèi)存語(yǔ)義般甲。

concurrent包的實(shí)現(xiàn)

由于java的CAS同時(shí)具有 volatile 讀和volatile寫的內(nèi)存語(yǔ)義肋乍,因此Java線程之間的通信現(xiàn)在有了下面四種方式:

  1. A線程寫volatile變量,隨后B線程讀這個(gè)volatile變量敷存。
  2. A線程寫volatile變量墓造,隨后B線程用CAS更新這個(gè)volatile變量。
  3. A線程用CAS更新一個(gè)volatile變量锚烦,隨后B線程用CAS更新這個(gè)volatile變量觅闽。
  4. A線程用CAS更新一個(gè)volatile變量,隨后B線程讀這個(gè)volatile變量涮俄。

Java的CAS會(huì)使用現(xiàn)代處理器上提供的高效機(jī)器級(jí)別原子指令蛉拙,這些原子指令以原子方式對(duì)內(nèi)存執(zhí)行讀-改-寫操作,這是在多處理器中實(shí)現(xiàn)同步的關(guān)鍵(從本質(zhì)上來(lái)說(shuō)彻亲,能夠支持原子性讀-改-寫指令的計(jì)算機(jī)器孕锄,是順序計(jì)算圖靈機(jī)的異步等價(jià)機(jī)器,因此任何現(xiàn)代的多處理器都會(huì)去支持某種能對(duì)內(nèi)存執(zhí)行原子性讀-改-寫操作的原子指令)苞尝。同時(shí)畸肆,volatile變量的讀/寫和CAS可以實(shí)現(xiàn)線程之間的通信。把這些特性整合在一起宙址,就形成了整個(gè)concurrent包得以實(shí)現(xiàn)的基石轴脐。如果我們仔細(xì)分析concurrent包的源代碼實(shí)現(xiàn),會(huì)發(fā)現(xiàn)一個(gè)通用化的實(shí)現(xiàn)模式:

  1. 首先抡砂,聲明共享變量為volatile大咱;
  2. 然后,使用CAS的原子條件更新來(lái)實(shí)現(xiàn)線程之間的同步注益;
  3. 同時(shí)徽级,配合以volatile的讀/寫和CAS所具有的volatile讀和寫的內(nèi)存語(yǔ)義來(lái)實(shí)現(xiàn)線程之間的通信。

AQS聊浅,非阻塞數(shù)據(jù)結(jié)構(gòu)和原子變量類(java.util.concurrent.atomic包中的類),這些concurrent包中的基礎(chǔ)類都是使用這種模式來(lái)實(shí)現(xiàn)的,而concurrent包中的高層類又是依賴于這些基礎(chǔ)類來(lái)實(shí)現(xiàn)的低匙。從整體來(lái)看旷痕,concurrent包的實(shí)現(xiàn)示意圖如下:

synchronized實(shí)現(xiàn)原理

轉(zhuǎn)自:https://blog.csdn.net/chenssy/article/details/54883355

記得剛剛開(kāi)始學(xué)習(xí)Java的時(shí)候,一遇到多線程情況就是synchronized顽冶。對(duì)于當(dāng)時(shí)的我們來(lái)說(shuō)欺抗,synchronized是如此的神奇且強(qiáng)大。我們賦予它一個(gè)名字“同步”强重,也成為我們解決多線程情況的良藥绞呈,百試不爽。但是间景,隨著學(xué)習(xí)的深入佃声,我們知道synchronized是一個(gè)重量級(jí)鎖,相對(duì)于Lock倘要,它會(huì)顯得那么笨重圾亏,以至于我們認(rèn)為它不是那么的高效,并慢慢拋棄它封拧。

誠(chéng)然志鹃,隨著Javs SE 1.6對(duì)synchronized進(jìn)行各種優(yōu)化后,synchronized不會(huì)顯得那么重泽西。

下面跟隨LZ一起來(lái)探索synchronized的實(shí)現(xiàn)機(jī)制曹铃、Java是如何對(duì)它進(jìn)行了優(yōu)化、鎖優(yōu)化機(jī)制捧杉、鎖的存儲(chǔ)結(jié)構(gòu)和升級(jí)過(guò)程陕见。

****1、實(shí)現(xiàn)原理****

synchronized可以保證方法或者代碼塊在運(yùn)行時(shí)糠溜,同一時(shí)刻只有一個(gè)方法可以進(jìn)入到臨界區(qū)淳玩,同時(shí)它還可以保證共享變量的內(nèi)存可見(jiàn)性。

Java中每一個(gè)對(duì)象都可以作為鎖非竿,這是synchronized實(shí)現(xiàn)同步的基礎(chǔ):

  1. 普通同步方法蜕着,鎖是當(dāng)前實(shí)例對(duì)象;

  2. 靜態(tài)同步方法红柱,鎖是當(dāng)前類的class對(duì)象承匣;

  3. 同步方法塊,鎖是括號(hào)里面的對(duì)象锤悄。

當(dāng)一個(gè)線程訪問(wèn)同步代碼塊時(shí)韧骗,它首先是需要得到鎖才能執(zhí)行同步代碼,當(dāng)退出或者拋出異常時(shí)必須要釋放鎖零聚,那么它是如何來(lái)實(shí)現(xiàn)這個(gè)機(jī)制的呢袍暴?

我們先看一段簡(jiǎn)單的代碼:

<pre>public class SynchronizedTest{ public synchronized void test1(){

} public void test2(){
    synchronized(this){

   }
}

}</pre>

利用Javap工具查看生成的class文件信息來(lái)分析Synchronize的實(shí)現(xiàn):

從上面可以看出些侍,同步代碼塊是使用monitorenter和monitorexit指令實(shí)現(xiàn)的,同步方法(在這看不出來(lái)需要看JVM底層實(shí)現(xiàn))依靠的是方法修飾符上的ACCSYNCHRONIZED實(shí)現(xiàn)政模。

同步代碼塊:

monitorenter指令插入到同步代碼塊的開(kāi)始位置岗宣,monitorexit指令插入到同步代碼塊的結(jié)束位置,JVM需要保證每一個(gè)monitorenter都有一個(gè)monitorexit與之相對(duì)應(yīng)淋样。任何對(duì)象都有一個(gè)monitor與之相關(guān)聯(lián)耗式,當(dāng)且一個(gè)monitor被持有之后,他將處于鎖定狀態(tài)趁猴。線程執(zhí)行到monitorenter指令時(shí)刊咳,將會(huì)嘗試獲取對(duì)象所對(duì)應(yīng)的monitor所有權(quán),即嘗試獲取對(duì)象的鎖儡司;

同步方法

synchronized方法則會(huì)被翻譯成普通的方法調(diào)用和返回指令如:invokevirtual娱挨、areturn指令,在VM字節(jié)碼層面并沒(méi)有任何特別的指令來(lái)實(shí)現(xiàn)被synchronized修飾的方法枫慷,而是在Class文件的方法表中將該方法的accessflags字段中的synchronized標(biāo)志位置1让蕾,表示該方法是同步方法并使用調(diào)用該方法的對(duì)象或該方法所屬的Class在JVM的內(nèi)部對(duì)象表示Klass做為鎖對(duì)象。

(摘自:http://www.cnblogs.com/javaminer/p/3889023.html)

下面我們來(lái)繼續(xù)分析或听,但是在深入之前我們需要了解兩個(gè)重要的概念:Java對(duì)象頭探孝、Monitor。

Java對(duì)象頭誉裆、monitor:Java對(duì)象頭和monitor是實(shí)現(xiàn)synchronized的基礎(chǔ)顿颅!下面就這兩個(gè)概念來(lái)做詳細(xì)介紹。

2足丢、Java對(duì)象頭

synchronized用的鎖是存在Java對(duì)象頭里的粱腻,那么什么是Java對(duì)象頭呢?

Hotspot虛擬機(jī)的對(duì)象頭主要包括兩部分?jǐn)?shù)據(jù):Mark Word(標(biāo)記字段)斩跌、Klass Pointer(類型指針)绍些。其中Klass Point是是對(duì)象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例耀鸦,Mark Word用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù)柬批,它是實(shí)現(xiàn)輕量級(jí)鎖和偏向鎖的關(guān)鍵。

所以下面將重點(diǎn)闡述袖订。

  • Mark Word

    Mark Word用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù)氮帐,如哈希碼(HashCode)、GC分代年齡洛姑、鎖狀態(tài)標(biāo)志上沐、線程持有的鎖、偏向線程 ID楞艾、偏向時(shí)間戳等等参咙。Java對(duì)象頭一般占有兩個(gè)機(jī)器碼(在32位虛擬機(jī)中龄广,1個(gè)機(jī)器碼等于4字節(jié),也就是32bit)昂勒,但是如果對(duì)象是數(shù)組類型蜀细,則需要三個(gè)機(jī)器碼,因?yàn)镴VM虛擬機(jī)可以通過(guò)Java對(duì)象的元數(shù)據(jù)信息確定Java對(duì)象的大小戈盈,但是無(wú)法從數(shù)組的元數(shù)據(jù)來(lái)確認(rèn)數(shù)組的大小,所以用一塊來(lái)記錄數(shù)組長(zhǎng)度谆刨。

下圖是Java對(duì)象頭的存儲(chǔ)結(jié)構(gòu)(32位虛擬機(jī)):

對(duì)象頭信息是與對(duì)象自身定義的數(shù)據(jù)無(wú)關(guān)的額外存儲(chǔ)成本塘娶,但是考慮到虛擬機(jī)的空間效率,Mark Word被設(shè)計(jì)成一個(gè)非固定的數(shù)據(jù)結(jié)構(gòu)以便在極小的空間內(nèi)存存儲(chǔ)盡量多的數(shù)據(jù)痊夭,它會(huì)根據(jù)對(duì)象的狀態(tài)復(fù)用自己的存儲(chǔ)空間刁岸,也就是說(shuō),Mark Word會(huì)隨著程序的運(yùn)行發(fā)生變化她我,變化狀態(tài)如下(32位虛擬機(jī)):

簡(jiǎn)單介紹了Java對(duì)象頭虹曙,我們下面再看Monitor。

3番舆、Monitor

什么是Monitor酝碳?

我們可以把它理解為一個(gè)同步工具,也可以描述為一種同步機(jī)制恨狈,它通常被描述為一個(gè)對(duì)象疏哗。

與一切皆對(duì)象一樣,所有的Java對(duì)象是天生的Monitor禾怠,每一個(gè)Java對(duì)象都有成為Monitor的潛質(zhì)返奉,因?yàn)樵贘ava的設(shè)計(jì)中 ,每一個(gè)Java對(duì)象自打娘胎里出來(lái)就帶了一把看不見(jiàn)的鎖吗氏,它叫做內(nèi)部鎖或者M(jìn)onitor鎖芽偏。

Monitor 是線程私有的數(shù)據(jù)結(jié)構(gòu),每一個(gè)線程都有一個(gè)可用monitor record列表弦讽,同時(shí)還有一個(gè)全局的可用列表污尉。每一個(gè)被鎖住的對(duì)象都會(huì)和一個(gè)monitor關(guān)聯(lián)(對(duì)象頭的MarkWord中的LockWord指向monitor的起始地址),同時(shí)monitor中有一個(gè)Owner字段存放擁有該鎖的線程的唯一標(biāo)識(shí)坦袍,表示該鎖被這個(gè)線程占用十厢。

其結(jié)構(gòu)如下:

  • Owner:初始時(shí)為NULL表示當(dāng)前沒(méi)有任何線程擁有該monitor record,當(dāng)線程成功擁有該鎖后保存線程唯一標(biāo)識(shí)捂齐,當(dāng)鎖被釋放時(shí)又設(shè)置為NULL蛮放。

  • EntryQ:關(guān)聯(lián)一個(gè)系統(tǒng)互斥鎖(semaphore),阻塞所有試圖鎖住monitor record失敗的線程奠宜。

  • RcThis:表示blocked或waiting在該monitor record上的所有線程的個(gè)數(shù)包颁。

  • Nest:用來(lái)實(shí)現(xiàn)重入鎖的計(jì)數(shù)瞻想。HashCode:保存從對(duì)象頭拷貝過(guò)來(lái)的HashCode值(可能還包含GC age)。

  • Candidate:用來(lái)避免不必要的阻塞或等待線程喚醒娩嚼,因?yàn)槊恳淮沃挥幸粋€(gè)線程能夠成功擁有鎖蘑险,如果每次前一個(gè)釋放鎖的線程喚醒所有正在阻塞或等待的線程,會(huì)引起不必要的上下文切換(從阻塞到就緒然后因?yàn)楦?jìng)爭(zhēng)鎖失敗又被阻塞)從而導(dǎo)致性能嚴(yán)重下降岳悟。

    Candidate只有兩種可能的值0表示沒(méi)有需要喚醒的線程1表示要喚醒一個(gè)繼任線程來(lái)競(jìng)爭(zhēng)鎖佃迄。

我們知道synchronized是重量級(jí)鎖,效率不怎么滴贵少,同時(shí)這個(gè)觀念也一直存在我們腦海里呵俏,不過(guò)在JDK 1.6中對(duì)synchronize的實(shí)現(xiàn)進(jìn)行了各種優(yōu)化,使得它顯得不是那么重了滔灶,那么JVM采用了那些優(yōu)化手段呢普碎?

4、鎖優(yōu)化

JDK1.6對(duì)鎖的實(shí)現(xiàn)引入了大量的優(yōu)化录平,如自旋鎖麻车、適應(yīng)性自旋鎖、鎖消除斗这、鎖粗化动猬、偏向鎖、輕量級(jí)鎖等技術(shù)來(lái)減少鎖操作的開(kāi)銷涝影。

  鎖主要存在四中狀態(tài)枣察,依次是:無(wú)鎖狀態(tài)、偏向鎖狀態(tài)燃逻、輕量級(jí)鎖狀態(tài)序目、重量級(jí)鎖狀態(tài)。他們會(huì)隨著競(jìng)爭(zhēng)的激烈而逐漸升級(jí)伯襟。注意鎖可以升級(jí)不可降級(jí)猿涨,這種策略是為了提高獲得鎖和釋放鎖的效率。

5姆怪、自旋鎖

線程的阻塞和喚醒需要CPU從用戶態(tài)轉(zhuǎn)為核心態(tài)叛赚,頻繁的阻塞和喚醒對(duì)CPU來(lái)說(shuō)是一件負(fù)擔(dān)很重的工作,勢(shì)必會(huì)給系統(tǒng)的并發(fā)性能帶來(lái)很大的壓力稽揭。同時(shí)我們發(fā)現(xiàn)在許多應(yīng)用上面俺附,對(duì)象鎖的鎖狀態(tài)只會(huì)持續(xù)很短一段時(shí)間為了這一段很短的時(shí)間頻繁地阻塞和喚醒線程是非常不值得的溪掀。

所以引入自旋鎖事镣。

何謂自旋鎖?

  所謂自旋鎖揪胃,就是讓該線程等待一段時(shí)間璃哟,不會(huì)被立即掛起(就是不讓前來(lái)獲取該鎖(已被占用)的線程立即阻塞)氛琢,看持有鎖的線程是否會(huì)很快釋放鎖。

怎么等待呢随闪?

執(zhí)行一段無(wú)意義的循環(huán)即可(自旋)阳似。

自旋等待不能替代阻塞,先不說(shuō)對(duì)處理器數(shù)量的要求(多核铐伴,貌似現(xiàn)在沒(méi)有單核的處理器了)撮奏,雖然它可以避免線程切換帶來(lái)的開(kāi)銷,但是它占用了處理器的時(shí)間当宴。如果持有鎖的線程很快就釋放了鎖挽荡,那么自旋的效率就非常好;反之即供,自旋的線程就會(huì)白白消耗掉處理的資源,它不會(huì)做任何有意義的工作于微,典型的占著茅坑不拉屎逗嫡,這樣反而會(huì)帶來(lái)性能上的浪費(fèi)。

所以說(shuō)株依,自旋等待的時(shí)間(自旋的次數(shù))必須要有一個(gè)限度驱证,如果自旋超過(guò)了定義的時(shí)間仍然沒(méi)有獲取到鎖,則應(yīng)該被掛起恋腕。自旋鎖在JDK 1.4.2中引入抹锄,默認(rèn)關(guān)閉,但是可以使用-XX:+UseSpinning開(kāi)開(kāi)啟荠藤,在JDK1.6中默認(rèn)開(kāi)啟伙单。同時(shí)自旋的默認(rèn)次數(shù)為10次,可以通過(guò)參數(shù)-XX:PreBlockSpin來(lái)調(diào)整哈肖。

如果通過(guò)參數(shù)-XX:preBlockSpin來(lái)調(diào)整自旋鎖的自旋次數(shù)吻育,會(huì)帶來(lái)諸多不便。假如我將參數(shù)調(diào)整為10淤井,但是系統(tǒng)很多線程都是等你剛剛退出的時(shí)候就釋放了鎖(假如你多自旋一兩次就可以獲取鎖)布疼,你是不是很尷尬?于是JDK1.6引入自適應(yīng)的自旋鎖币狠,讓虛擬機(jī)會(huì)變得越來(lái)越聰明游两。

6、適應(yīng)自旋鎖

JDK 1.6引入了更加聰明的自旋鎖漩绵,即自適應(yīng)自旋鎖贱案。所謂自適應(yīng)就意味著自旋的次數(shù)不再是固定的,它是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的狀態(tài)來(lái)決定渐行。

它怎么做呢轰坊?

線程如果自旋成功了铸董,那么下次自旋的次數(shù)會(huì)更加多,因?yàn)樘摂M機(jī)認(rèn)為既然上次成功了肴沫,那么此次自旋也很有可能會(huì)再次成功粟害,那么它就會(huì)允許自旋等待持續(xù)的次數(shù)更多。反之颤芬,如果對(duì)于某個(gè)鎖悲幅,很少有自旋能夠成功的,那么在以后要或者這個(gè)鎖的時(shí)候自旋的次數(shù)會(huì)減少甚至省略掉自旋過(guò)程站蝠,以免浪費(fèi)處理器資源汰具。有了自適應(yīng)自旋鎖,隨著程序運(yùn)行和性能監(jiān)控信息的不斷完善菱魔,虛擬機(jī)對(duì)程序鎖的狀況預(yù)測(cè)會(huì)越來(lái)越準(zhǔn)確留荔,虛擬機(jī)會(huì)變得越來(lái)越聰明。

7澜倦、鎖消除

為了保證數(shù)據(jù)的完整性聚蝶,我們?cè)谶M(jìn)行操作時(shí)需要對(duì)這部分操作進(jìn)行同步控制,但是在有些情況下藻治,JVM檢測(cè)到不可能存在共享數(shù)據(jù)競(jìng)爭(zhēng)碘勉,這是JVM會(huì)對(duì)這些同步鎖進(jìn)行鎖消除。鎖消除的依據(jù)是逃逸分析的數(shù)據(jù)支持桩卵。

  如果不存在競(jìng)爭(zhēng)验靡,為什么還需要加鎖呢?

所以鎖消除可以節(jié)省毫無(wú)意義的請(qǐng)求鎖的時(shí)間雏节。變量是否逃逸胜嗓,對(duì)于虛擬機(jī)來(lái)說(shuō)需要使用數(shù)據(jù)流分析來(lái)確定,但是對(duì)于我們程序員來(lái)說(shuō)這還不清楚么矾屯?我們會(huì)在明明知道不存在數(shù)據(jù)競(jìng)爭(zhēng)的代碼塊前加上同步嗎兼蕊?但是有時(shí)候程序并不是我們所想的那樣?

我們雖然沒(méi)有顯示使用鎖件蚕,但是我們?cè)谑褂靡恍㎎DK的內(nèi)置API時(shí)孙技,如StringBuffer、Vector排作、HashTable等少梁,這個(gè)時(shí)候會(huì)存在隱形的加鎖操作滋觉。

  比如StringBuffer的append()方法,Vector的add()方法:

<pre>public void vectorTest(){
Vector<String> vector = new Vector<String>(); for(int i = 0 ; i < 10 ; i++){
vector.add(i + "");
}

System.out.println(vector);

}</pre>

在運(yùn)行這段代碼時(shí),JVM可以明顯檢測(cè)到變量vector沒(méi)有逃逸出方法vectorTest()之外喉镰,所以JVM可以大膽地將vector內(nèi)部的加鎖操作消除。

8、鎖粗化

我們知道在使用同步鎖的時(shí)候,需要讓同步塊的作用范圍盡可能小土浸,僅在共享數(shù)據(jù)的實(shí)際作用域中才進(jìn)行同步。這樣做的目的是為了使需要同步的操作數(shù)量盡可能縮小彭羹,如果存在鎖競(jìng)爭(zhēng)黄伊,那么等待鎖的線程也能盡快拿到鎖。

在大多數(shù)的情況下派殷,上述觀點(diǎn)是正確的还最,LZ也一直堅(jiān)持著這個(gè)觀點(diǎn)。但是如果一系列的連續(xù)加鎖解鎖操作毡惜,可能會(huì)導(dǎo)致不必要的性能損耗拓轻,所以引入鎖粗化的概念。

那什么是鎖粗化经伙?

就是將多個(gè)連續(xù)的加鎖扶叉、解鎖操作連接在一起,擴(kuò)展成一個(gè)范圍更大的鎖帕膜。

如上面實(shí)例:vector每次add的時(shí)候都需要加鎖操作辜梳,JVM檢測(cè)到對(duì)同一個(gè)對(duì)象(vector)連續(xù)加鎖、解鎖操作泳叠,會(huì)合并一個(gè)更大范圍的加鎖、解鎖操作茶宵,即加鎖解鎖操作會(huì)移到for循環(huán)之外危纫。

9、輕量級(jí)鎖

引入輕量級(jí)鎖的主要目的是在多沒(méi)有多線程競(jìng)爭(zhēng)的前提下乌庶,減少傳統(tǒng)的重量級(jí)鎖使用操作系統(tǒng)互斥量產(chǎn)生的性能消耗种蝶。

當(dāng)關(guān)閉偏向鎖功能或者多個(gè)線程競(jìng)爭(zhēng)偏向鎖導(dǎo)致偏向鎖升級(jí)為輕量級(jí)鎖,則會(huì)嘗試獲取輕量級(jí)鎖瞒大,其步驟如下:****獲取鎖螃征。

  1. 判斷當(dāng)前對(duì)象是否處于無(wú)鎖狀態(tài)(hashcode、0透敌、01)盯滚,若是,則JVM首先將在當(dāng)前線程的棧幀中建立一個(gè)名為鎖記錄(Lock Record)的空間酗电,用于存儲(chǔ)鎖對(duì)象目前的Mark Word的拷貝(官方把這份拷貝加了一個(gè)Displaced前綴魄藕,即Displaced Mark Word);否則執(zhí)行步驟(3)撵术;

  2. JVM利用CAS操作嘗試將對(duì)象的Mark Word更新為指向Lock Record的指正背率,如果成功表示競(jìng)爭(zhēng)到鎖,則將鎖標(biāo)志位變成00(表示此對(duì)象處于輕量級(jí)鎖狀態(tài)),執(zhí)行同步操作寝姿;如果失敗則執(zhí)行步驟(3)交排;

  3. 判斷當(dāng)前對(duì)象的Mark Word是否指向當(dāng)前線程的棧幀,如果是則表示當(dāng)前線程已經(jīng)持有當(dāng)前對(duì)象的鎖饵筑,則直接執(zhí)行同步代碼塊埃篓;否則只能說(shuō)明該鎖對(duì)象已經(jīng)被其他線程搶占了,這時(shí)輕量級(jí)鎖需要膨脹為重量級(jí)鎖翻翩,鎖標(biāo)志位變成10都许,后面等待的線程將會(huì)進(jìn)入阻塞狀態(tài);

釋放鎖輕量級(jí)鎖的釋放也是通過(guò)CAS操作來(lái)進(jìn)行的嫂冻,主要步驟如下:

  1. 取出在獲取輕量級(jí)鎖保存在Displaced Mark Word中的數(shù)據(jù)胶征;

  2. 用CAS操作將取出的數(shù)據(jù)替換當(dāng)前對(duì)象的Mark Word中,如果成功桨仿,則說(shuō)明釋放鎖成功睛低,否則執(zhí)行(3);

  3. 如果CAS操作替換失敗服傍,說(shuō)明有其他線程嘗試獲取該鎖钱雷,則需要在釋放鎖的同時(shí)需要喚醒被掛起的線程。

輕量級(jí)鎖能提升程序同步性能的依據(jù)是“對(duì)于絕大部分的鎖吹零,在整個(gè)同步周期內(nèi)都是不存在競(jìng)爭(zhēng)的”罩抗,這是一個(gè)經(jīng)驗(yàn)數(shù)據(jù)。輕量級(jí)鎖在當(dāng)前線程的棧幀中建立一個(gè)名為鎖記錄的空間灿椅,用于存儲(chǔ)鎖對(duì)象目前的指向和狀態(tài)套蒂。如果沒(méi)有競(jìng)爭(zhēng),輕量級(jí)鎖使用CAS操作避免了使用互斥量的開(kāi)銷茫蛹,但如果存在鎖競(jìng)爭(zhēng)操刀,除了互斥量的開(kāi)銷外,還額外發(fā)生了CAS操作婴洼,因此在有競(jìng)爭(zhēng)的情況下骨坑,輕量級(jí)鎖會(huì)比傳統(tǒng)的重量級(jí)鎖更慢。

什么是CAS操作柬采?

compare and swap,CAS操作需要輸入兩個(gè)數(shù)值欢唾,一個(gè)舊值(期望操作前的值)和一個(gè)新值,在操作期間先比較舊值有沒(méi)有發(fā)生變化粉捻,如果沒(méi)有發(fā)生變化匈辱,才交換成新值,發(fā)生了變化則不交換杀迹。

CAS詳解:https://mp.weixin.qq.com/s__biz=MzIxMjE5MTE1Nw==&mid=2653192625&idx=1&sn=cbabbd806e4874e8793332724ca9d454&chksm=8c99f36bbbee7a7d169581dedbe09658d0b0edb62d2cbc9ba4c40f706cb678c7d8c768afb666&scene=21#wechat_redirect

https://blog.csdn.net/qq_35357656/article/details/78657373

下圖是輕量級(jí)鎖的獲取和釋放過(guò)程:

10亡脸、偏向鎖

引入偏向鎖主要目的是:為了在無(wú)多線程競(jìng)爭(zhēng)的情況下盡量減少不必要的輕量級(jí)鎖執(zhí)行路徑押搪。上面提到了輕量級(jí)鎖的加鎖解鎖操作是需要依賴多次CAS原子指令的。那么偏向鎖是如何來(lái)減少不必要的CAS操作呢浅碾?我們可以查看Mark work的結(jié)構(gòu)就明白了大州。

只需要檢查是否為偏向鎖、鎖標(biāo)識(shí)為以及ThreadID即可垂谢,處理流程如下:****獲取鎖厦画。

  1. 檢測(cè)Mark Word是否為可偏向狀態(tài),即是否為偏向鎖1滥朱,鎖標(biāo)識(shí)位為01根暑;

  2. 若為可偏向狀態(tài),則測(cè)試線程ID是否為當(dāng)前線程ID徙邻,如果是排嫌,則執(zhí)行步驟(5),否則執(zhí)行步驟(3)缰犁;

  3. 如果線程ID不為當(dāng)前線程ID淳地,則通過(guò)CAS操作競(jìng)爭(zhēng)鎖,競(jìng)爭(zhēng)成功帅容,則將Mark Word的線程ID替換為當(dāng)前線程ID颇象,否則執(zhí)行線程(4);

  4. 通過(guò)CAS競(jìng)爭(zhēng)鎖失敗并徘,證明當(dāng)前存在多線程競(jìng)爭(zhēng)情況遣钳,當(dāng)?shù)竭_(dá)全局安全點(diǎn),獲得偏向鎖的線程被掛起麦乞,偏向鎖升級(jí)為輕量級(jí)鎖耍贾,然后被阻塞在安全點(diǎn)的線程繼續(xù)往下執(zhí)行同步代碼塊;

  5. 執(zhí)行同步代碼塊路幸。

釋放鎖偏向鎖的釋放采用了一種只有競(jìng)爭(zhēng)才會(huì)釋放鎖的機(jī)制,線程是不會(huì)主動(dòng)去釋放偏向鎖付翁,需要等待其他線程來(lái)競(jìng)爭(zhēng)简肴。偏向鎖的撤銷需要等待全局安全點(diǎn)(這個(gè)時(shí)間點(diǎn)是上沒(méi)有正在執(zhí)行的代碼)。

其步驟如下:

  1. 暫停擁有偏向鎖的線程百侧,判斷鎖對(duì)象石是否還處于被鎖定狀態(tài)砰识;

  2. 撤銷偏向蘇,恢復(fù)到無(wú)鎖狀態(tài)(01)或者輕量級(jí)鎖的狀態(tài)佣渴。

下圖是偏向鎖的獲取和釋放流程:

11辫狼、重量級(jí)鎖

重量級(jí)鎖通過(guò)對(duì)象內(nèi)部的監(jiān)視器(monitor)實(shí)現(xiàn),其中monitor的本質(zhì)是依賴于底層操作系統(tǒng)的Mutex Lock實(shí)現(xiàn)辛润,操作系統(tǒng)實(shí)現(xiàn)線程之間的切換需要從用戶態(tài)到內(nèi)核態(tài)的切換膨处,切換成本非常高。

參考資料

  1. 周志明:《深入理解Java虛擬機(jī)》
  2. 方騰飛:《Java并發(fā)編程的藝術(shù)》
  3. Java中synchronized的實(shí)現(xiàn)原理與應(yīng)用
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市真椿,隨后出現(xiàn)的幾起案子鹃答,更是在濱河造成了極大的恐慌,老刑警劉巖突硝,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件测摔,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡解恰,警方通過(guò)查閱死者的電腦和手機(jī)锋八,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)护盈,“玉大人挟纱,你說(shuō)我怎么就攤上這事』魄恚” “怎么了樊销?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)脏款。 經(jīng)常有香客問(wèn)我围苫,道長(zhǎng),這世上最難降的妖魔是什么撤师? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任剂府,我火速辦了婚禮,結(jié)果婚禮上剃盾,老公的妹妹穿的比我還像新娘腺占。我一直安慰自己,他們只是感情好痒谴,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布衰伯。 她就那樣靜靜地躺著,像睡著了一般积蔚。 火紅的嫁衣襯著肌膚如雪意鲸。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,370評(píng)論 1 302
  • 那天尽爆,我揣著相機(jī)與錄音怎顾,去河邊找鬼。 笑死漱贱,一個(gè)胖子當(dāng)著我的面吹牛槐雾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播幅狮,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼募强,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼株灸!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起钻注,我...
    開(kāi)封第一講書(shū)人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蚂且,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后幅恋,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體杏死,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年捆交,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了淑翼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡品追,死狀恐怖玄括,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情肉瓦,我是刑警寧澤遭京,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站泞莉,受9級(jí)特大地震影響哪雕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鲫趁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一斯嚎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧挨厚,春花似錦堡僻、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至巢价,卻和暖如春牲阁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蹄溉。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留您炉,地道東北人柒爵。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像赚爵,于是被迫代替她去往敵國(guó)和親棉胀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子法瑟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容