volatile丽啡、synchronized、lock詳解

volatile硬猫、synchronized补箍、lock詳解

1改执、volatile

被volatile定義的變量被一個(gè)線程修改后,另一個(gè)線程可以感知到坑雅。

能夠保證讀的準(zhǔn)確性辈挂,不能保證寫的準(zhǔn)確性

1.1 機(jī)理

volatile意味著可見性。先看一個(gè)例子:

static class MyRunnable implements Runnable {
    private boolean isRun = true;

    public void setRun(boolean run) {
        isRun = run;
    }

    @Override
    public void run() {
        System.out.println("線程1 開始運(yùn)行..." + DateUtils.INSTANCE.getCurrDataStr());
        // 當(dāng) isRun 為true時(shí)裹粤,會(huì)一直循環(huán)终蒂。直至isRun為false
        while (isRun) {

        }
        System.out.println("線程1 運(yùn)行結(jié)束了..." + DateUtils.INSTANCE.getCurrDataStr());
    }
}

再另啟線程2去改變 isRun 的值,讓線程1退出循環(huán)

public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(2);

    MyRunnable myRunnable = new MyRunnable();
    executorService.execute(myRunnable);
    executorService.execute(new Runnable() {
        @Override
        public void run() {
            System.out.println("線程2 開始運(yùn)行..." + DateUtils.INSTANCE.getCurrDataStr());

            try {
                Thread.sleep(1000);
                System.out.println("線程2 等待2s后遥诉,去停止運(yùn)行線程1..." + DateUtils.INSTANCE.getCurrDataStr());

                myRunnable.setRun(false);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
}

打印的結(jié)果可以看到拇泣,線程1并沒有退出循環(huán)。因?yàn)榫€程2將 isRun 變量讀取到它的內(nèi)存空間進(jìn)行修改后矮锈,寫入主內(nèi)存霉翔,但由于線程1一直在私有棧中讀取 isRun 變量,沒有在主內(nèi)存中讀取 isRun 變量愕难,因此不會(huì)退出循環(huán)早龟。

如果我們把isRun賦值行改為:

private volatile boolean isRun = true;

將其用 volatile 修飾惫霸,則強(qiáng)制該變量從主內(nèi)存中讀取猫缭。

這樣我們也就明白了volatile的實(shí)現(xiàn)機(jī)理,即:

  1. 當(dāng)一個(gè)線程要使用 volatile 變量時(shí)壹店,它會(huì)直接從主內(nèi)存中讀取猜丹,而不使用自己工作內(nèi)存中的副本。
  2. 當(dāng)一個(gè)線程對(duì)一個(gè) volatile 變量寫時(shí)硅卢,它會(huì)將變量的值刷新到共享內(nèi)存(主內(nèi)存)中射窒。

1.2 非原子性

原子性是指,對(duì)于一個(gè)操作将塑,其操作的內(nèi)容只有全部執(zhí)行/全不執(zhí)行兩個(gè)狀態(tài)脉顿,不存在中間態(tài)。而volatile不能鎖定某組操作点寥,防止其他線程的干擾艾疟,即沒有其他線程的干擾,所以volatile是非原子性的敢辩。也就是非線程安全的蔽莱。

所以如果想要使用一個(gè)原子性的修飾符來控制操作,即在操作變量時(shí)鎖定變量戚长,我們就需要另一個(gè)修飾詞synchronized盗冷。

2、synchronized

synchronized作用的代碼范圍對(duì)于不同線程是互斥的同廉,并且線程子啊釋放鎖的時(shí)候會(huì)將共享變量的值刷新到共享內(nèi)存中仪糖。

對(duì)象設(shè)置:

修飾代碼塊時(shí)柑司,需1個(gè)reference對(duì)象作為鎖的對(duì)象

修飾實(shí)例方法時(shí),默認(rèn)的鎖對(duì)象 = 當(dāng)前對(duì)象

修飾類方法(靜態(tài))時(shí)锅劝,默認(rèn)的鎖對(duì)象 = 當(dāng)前類的Class對(duì)象

2.1 synchronized與volatile的區(qū)別

1帜羊、使用:

volatile只可以修飾變量,synchronized可以修飾對(duì)象鸠天,類讼育,方法,代碼塊稠集,語(yǔ)句奶段。

2、原子性:

volatile是非原子性的剥纷,只能保證讀的準(zhǔn)確性痹籍,不能保證寫的準(zhǔn)確性。多線程并發(fā)訪問變量時(shí)晦鞋,不會(huì)產(chǎn)生阻塞蹲缠。synchronized是原子性的,只有獲取的鎖的線程才能進(jìn)入臨界區(qū)悠垛,從而保證了臨界區(qū)的所有語(yǔ)句全部執(zhí)行线定。多線程并發(fā)訪問會(huì)產(chǎn)生阻塞。

3确买、機(jī)理:

當(dāng)線程對(duì)volatile變量讀時(shí)斤讥,會(huì)把工作內(nèi)存中的值置為無效。當(dāng)線程對(duì)synchronized變量讀時(shí)湾趾,會(huì)在改線程鎖定變量時(shí)將工作內(nèi)存中的變量置為無效芭商。

當(dāng)線程對(duì)volalite變量寫時(shí),會(huì)把修改的值刷新到主內(nèi)存搀缠。當(dāng)線程對(duì)synchronized變量寫時(shí)铛楣,會(huì)在線程釋放鎖的時(shí)候,將修改的值刷新到主內(nèi)存艺普。

2.2 注意點(diǎn)

1簸州、無論synchronized加在方法上還是對(duì)象上,其修飾的都是對(duì)象衷敌,而不是方法或者某個(gè)代碼塊代碼語(yǔ)句

2勿侯、每個(gè)對(duì)象只有一個(gè)鎖與之相對(duì)聯(lián)

3、實(shí)現(xiàn)同步需要很大的系統(tǒng)開銷來做控制缴罗,不要做無謂的鎖定

2.3 synchronized的作用域

synchronized的作用域只有兩種助琐。實(shí)際上,synchronized直接作用于內(nèi)存中的一個(gè)內(nèi)存塊面氓,因此兵钮,可以通過鎖定內(nèi)存塊來鎖定一個(gè)實(shí)例變量或者鎖定一個(gè)靜態(tài)區(qū)域蛆橡。

1、某個(gè)對(duì)象實(shí)例內(nèi)

synchronized aMethod(){} 可以防止多個(gè)線程同時(shí)訪問這個(gè)對(duì)象的synchronized方法掘譬,如果對(duì)象有多個(gè)synchronized方法泰演,則只要一個(gè)線程訪問了任何一個(gè)synchronized方法,則其他線程都不能同時(shí)訪問任何一個(gè)該對(duì)象的synchronized方法葱轩。(synchronized作用于對(duì)象睦焕,且每個(gè)對(duì)象只有一個(gè)鎖)

2、某個(gè)類的范圍

又或者說是作用于靜態(tài)方法/靜態(tài)代碼塊靴拱。synchronized static aMethod(){} 可以防止多個(gè)線程同時(shí)訪問這個(gè)類中的synchronized static 方法垃喊,它可以對(duì)類的所有實(shí)例對(duì)象起作用。

2.4 synchronized應(yīng)用

2.4.1 synchronized方法

每個(gè)實(shí)例對(duì)應(yīng)一個(gè)lock袜炕,線程后的該含有synchronized方法的實(shí)例的鎖才可以執(zhí)行本谜,否則一直阻塞。方法一旦執(zhí)行偎窘,則一直到方法返回才可以釋放鎖乌助。此后被阻塞的線程才能獲得該鎖。對(duì)于一個(gè)實(shí)例陌知,其聲明為synchronized的方法顯然只有一個(gè)能處于執(zhí)行狀態(tài)他托。從而避免了類訪問變量的沖突。

synchronized同步的開銷很大纵诞,如果synchronized作用于一個(gè)比較大的方法上上祈,顯然是不合算的。

2.4.2 synchronized代碼塊

synchronized代碼塊形式如下:

synchronized (synchronizedObject){
    //Some thing
}

代碼塊內(nèi)部代碼必須在獲得synchronizedObject的鎖時(shí)才能執(zhí)行浙芙。需要重點(diǎn)說的是synchronized(this),這也是比較常用的代碼塊籽腕。

synchronized的效果類似于在方法前修飾嗡呼,只是修飾的方位縮小成代碼塊。兩個(gè)線程同時(shí)訪問一個(gè)變量時(shí)皇耗,如果一個(gè)線程在執(zhí)行synchronized的代碼南窗,那么該實(shí)例被鎖定,另一個(gè)線程如果要訪問該實(shí)例被synchronized作用的范圍郎楼,則會(huì)被阻塞万伤。

此外,如果不使用this作為鎖呜袁,而是只是想讓一段代碼同步敌买,可以臨時(shí)創(chuàng)建如下鎖:

private byte[] lock=new byte[0];

從操作碼上講,創(chuàng)建一個(gè)長(zhǎng)度為0的數(shù)組對(duì)象是最經(jīng)濟(jì)的阶界,只需要3條操作碼虹钮。

2.4.3. synchronized靜態(tài)方法

synchronized修飾靜態(tài)方法時(shí)或者在普通方法中以類為對(duì)象如下形式:

class StaticSynchronized{
    public void aMethod{
        synchronized (StaticSynchronized.class){
            //Some thing
        }
    }
}

為synchronized靜態(tài)方法聋庵。

注意的是,對(duì)于同一個(gè)類芙粱,其static和實(shí)例方法如果都用synchronized修飾祭玉,其作用的必然不是同一個(gè)對(duì)象。

2.4.4. synchronized對(duì)象

比較簡(jiǎn)單粗暴的實(shí)現(xiàn)形式春畔,直接把對(duì)象鎖定脱货,思路也很清晰。java負(fù)責(zé)跟蹤被加鎖的對(duì)象律姨,該鎖定對(duì)象的線程每次給對(duì)象加鎖時(shí)對(duì)象的計(jì)數(shù)器+1蹭劈,每次解鎖時(shí)計(jì)數(shù)器-1,如果對(duì)象的計(jì)數(shù)器為0线召,那么解除該線程的鎖定铺韧。

2.5 synchronized的缺陷

synchronized修飾的代碼只有獲取到鎖的線程才能夠執(zhí)行,其他線程只能等待該線程釋放鎖缓淹。一個(gè)線程釋放鎖的情況有以下方法:

  • 獲取鎖的線程完成了synchronized修飾的代碼塊的執(zhí)行哈打;
  • 線程執(zhí)行時(shí)發(fā)生異常,JVM自動(dòng)釋放鎖讯壶。

鎖會(huì)因?yàn)榈却齀/O料仗,sleep()方法等原因被阻塞而不釋放鎖,此時(shí)如果線程還處于用synchronized修飾的代碼塊區(qū)域里伏蚊,那么其他線程只能等待立轧,這樣就影響了效率。因此java提供了Lock來實(shí)現(xiàn)另一個(gè)機(jī)制躏吊,即不讓線程無限期的等待下去氛改。

思考一個(gè)場(chǎng)景,當(dāng)多個(gè)線程讀寫文件時(shí)比伏,讀操作和寫操作會(huì)發(fā)生沖突胜卤,寫操作和寫操作會(huì)發(fā)生沖突,讀和讀操作不會(huì)發(fā)生沖突赁项。如果使用synchronized來修飾讀和寫的話葛躏,就很可能操作多個(gè)讀操作無法同時(shí)進(jìn)行的可能。如果只有synchronized修飾寫的話悠菜,又有可能造成讀寫沖突舰攒。此時(shí)就需要用到Lock。

3悔醋、Lock

Lock不是語(yǔ)言內(nèi)置的摩窃,synchronized是java關(guān)鍵字,為內(nèi)置特性篙顺。Lock是一個(gè)類偶芍,通過這個(gè)類可以實(shí)現(xiàn)同步訪問充择。

使用synchronized不需要我們手動(dòng)的去控制加鎖和縮放,系統(tǒng)會(huì)自動(dòng)控制匪蟀。而使用Lock類需要手動(dòng)的加鎖和釋放椎麦。不主動(dòng)釋放可能會(huì)造成死鎖。

3.1 Lock類接口設(shè)計(jì)

public interface Lock {

    // 獲取鎖材彪。
    // 如果鎖不可用观挎,則當(dāng)前線程將被禁用以用于線程調(diào)度目的并處于休眠狀態(tài),直到獲得鎖為止段化。
    void lock();

    // 與lock用法一樣嘁捷。
    // 與lock的區(qū)別是:
    // lockInterruptibly鎖定的線程處于等待狀態(tài)時(shí),允許其他線程的打斷显熏。比如說獲取到鎖的線程A可以調(diào)用線程B.interrupt()雄嚣,來中斷等待中的線程B,并拋出一個(gè)interruptException;
    // lock鎖定的線程如果在等待時(shí)檢測(cè)到線程使用interrupt喘蟆,則會(huì)繼續(xù)嘗試獲取鎖缓升,失敗則繼續(xù)失眠,只是在成功獲取到鎖之后再把當(dāng)前線程置為interrupt狀態(tài)蕴轨。
    void lockInterruptibly() throws InterruptedException;
    
    // 如果鎖可用港谊,則獲取鎖并立即返回值為true 。如果鎖不可用橙弱,則此方法將立即返回值false 歧寺。
    boolean tryLock();

    // 設(shè)定了一個(gè)等待時(shí)間,如果在這個(gè)時(shí)間內(nèi)獲取到了鎖棘脐,則返回true斜筐,否色返回false結(jié)束
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    
    // 釋放鎖。一般放在異常處理操作的finally字符控制的代碼塊中荆残。
    void unlock();

    // 返回綁定到此Lock實(shí)例的新Condition實(shí)例奴艾。
    // 在等待條件之前,鎖必須由當(dāng)前線程持有内斯。調(diào)用Condition.await()將在等待之前自動(dòng)釋放鎖,并在等待返回之前重新獲取鎖像啼。
    Condition newCondition();
}

3.2 ReentrantLock可重入鎖

ReentrantLock是一個(gè)類俘闯,實(shí)現(xiàn)了Lock接口。

3.2.1 可重入定義

若一個(gè)程序或子程序可以“在任意時(shí)刻被中斷然后操作系統(tǒng)調(diào)度執(zhí)行另外一段代碼忽冻,這段代碼又調(diào)用了該子程序不會(huì)出錯(cuò)”真朗,則稱之為可重入。即當(dāng)該子程序正在運(yùn)行時(shí)僧诚,執(zhí)行線程可以再次進(jìn)入并執(zhí)行它遮婶,仍然獲得符合設(shè)計(jì)時(shí)預(yù)期的結(jié)果蝗碎。與多線程并發(fā)執(zhí)行的線程安全不同,可重入強(qiáng)調(diào)對(duì)單個(gè)線程執(zhí)行時(shí)重新進(jìn)入同一個(gè)子線程仍然是安全的旗扑。

3.2.2 可重入的條件

  • 不在函數(shù)內(nèi)使用靜態(tài)或全局?jǐn)?shù)據(jù)
  • 不返回靜態(tài)或全局?jǐn)?shù)據(jù)蹦骑,所有數(shù)據(jù)都由函數(shù)的調(diào)用者提供
  • 使用本地?cái)?shù)據(jù)(工作內(nèi)存),或者通過制作全局?jǐn)?shù)據(jù)的本地拷貝來保護(hù)全局?jǐn)?shù)據(jù)
  • 不調(diào)用不可重入函數(shù)

3.2.3 可重入與線程安全

可重入一般都是線程安全的臀防。但線程安全的不一定都是可重入的眠菇。在不加鎖的前提下,如果一個(gè)函數(shù)用到了全局或者靜態(tài)變量袱衷,那么它不是線程安全的捎废,也不是可重入的。如果我們加以改進(jìn)致燥,對(duì)全局變量的訪問加鎖登疗,此時(shí)它是線程安全的但不是可重入的,因?yàn)橥ǔ5募渔i方式是針對(duì)不同線程的訪問嫌蚤,當(dāng)同一個(gè)線程多次訪問就會(huì)產(chǎn)生問題辐益。只有當(dāng)函數(shù)滿足可重入的四條件時(shí),才是可重入的搬葬。

3.2.4 synchronized是可重入鎖

如果一個(gè)獲取到鎖1的線程A荷腊,請(qǐng)求一個(gè)被線程B持有的鎖2時(shí),則線程A會(huì)進(jìn)入到阻塞狀態(tài)急凰。如果線程A還是請(qǐng)求鎖1女仰,鎖1是可重入鎖,請(qǐng)求就會(huì)成功抡锈。

synchronized擁有強(qiáng)制原子性的內(nèi)部鎖機(jī)制疾忍,是一個(gè)可重入鎖。因此在一個(gè)線程使用synchronized方法時(shí)床三,調(diào)用該對(duì)象的另一個(gè)synchronized方法一罩,即一個(gè)線程的得到一個(gè)對(duì)象鎖之后再次請(qǐng)求該對(duì)象鎖是永遠(yuǎn)可以拿到的。

3.2.5 synchronized可重入鎖的實(shí)現(xiàn)

前面提到過撇簿,每個(gè)鎖關(guān)聯(lián)了一個(gè)線程持有者和一個(gè)計(jì)數(shù)器聂渊。當(dāng)計(jì)數(shù)器為0時(shí),說明沒有被任何線程所持有四瘫,那么任何線程都可能獲得該鎖而調(diào)用相應(yīng)的方法汉嗽。當(dāng)一個(gè)線程請(qǐng)求鎖成功后,jvm就會(huì)記錄下持有鎖的線程找蜜,并將計(jì)數(shù)器+1饼暑。此時(shí)其他線程請(qǐng)求該鎖,則必須等待。而是已經(jīng)持有該鎖的線程再次請(qǐng)求該所弓叛,則可以再次拿到這個(gè)鎖彰居,同時(shí)計(jì)數(shù)器會(huì)繼續(xù)+1。當(dāng)線程每退出一個(gè)synchronized方法/代碼塊時(shí)撰筷,計(jì)時(shí)器就會(huì)-1陈惰,直至為0,釋放該鎖闭专。

3.2.6 ReentrantLock原理

ReentrantLock主要是利用CAS和AQS隊(duì)列來實(shí)現(xiàn)的奴潘。支持公平鎖和非公平鎖,兩者實(shí)現(xiàn)類似影钉。

CAS

compare and swap画髓。比較并交換。CAS有3個(gè)操作數(shù):內(nèi)存值V平委,預(yù)期值A(chǔ)奈虾、要修改的新值B。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí)廉赔,才將內(nèi)存值V修改為B肉微,否則什么都不做。無論是哪種情況蜡塌,它都會(huì)在CAS指令前返回該位置的值碉纳。該操作是一個(gè)原子操作,被廣泛的應(yīng)用在java的底層實(shí)現(xiàn)中馏艾。在java中劳曹,CAS主要是由sun.misc.Unsafe這個(gè)類通過JNI調(diào)用CPU底層指令實(shí)現(xiàn)。

AQS

AbstractQueuedSynchronizer琅摩。是一個(gè)用于構(gòu)建鎖和同步容器的框架铁孵。事實(shí)上java.util.concurrent包內(nèi)許多類都是基于AQS構(gòu)建,例如ReentrantLock房资、CountDownLatch蜕劝、FurureTask等。AQS解決了在實(shí)現(xiàn)同步容器時(shí)設(shè)計(jì)的大量細(xì)節(jié)問題轰异。

AQS是一個(gè)FIFO隊(duì)列岖沛,代表排隊(duì)等待鎖的線程。隊(duì)列頭節(jié)點(diǎn)稱作“哨兵節(jié)點(diǎn)”或者“啞節(jié)點(diǎn)”搭独,它不與任何線程關(guān)聯(lián)烫止。其他節(jié)點(diǎn)與等待線程關(guān)聯(lián),每個(gè)節(jié)點(diǎn)維護(hù)一個(gè)等待狀態(tài)waitStatus戳稽。

ReentrantLock的基本實(shí)現(xiàn)可以概括為:先通過CAS嘗試獲取鎖。如果此時(shí)已經(jīng)有線程占據(jù)了鎖,那就加入AQS隊(duì)列并且被掛起惊奇。當(dāng)鎖被釋放之后互躬,排在CLH隊(duì)列隊(duì)首的線程會(huì)被喚醒,然后CAS再次嘗試獲取鎖颂郎。在這個(gè)時(shí)候吼渡,如果同時(shí)還有另一個(gè)線程來嘗試獲取鎖,當(dāng)這個(gè)鎖是非公平鎖時(shí)乓序,則有可能被這個(gè)線程搶先獲取到寺酪。但如果鎖是公平鎖,它就會(huì)發(fā)現(xiàn)自己不是在隊(duì)首的話替劈,就會(huì)排到隊(duì)尾寄雀,由隊(duì)首的線程去獲取鎖。

ReentrantLock提供了兩個(gè)構(gòu)造器陨献,默認(rèn)是非公平鎖盒犹。由lock()和unlock的源碼可以看到,它 們只是分別調(diào)用了sync對(duì)象的lock()和release(1)方法眨业。

NonfairSync
final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

首先用一個(gè)CAS操作急膀,判斷state是否為0(表示當(dāng)前鎖未被占用),如果是0龄捡,則把它置為1卓嫂,并且設(shè)置當(dāng)前線程為該鎖的獨(dú)占線程,表示獲取鎖成功聘殖。當(dāng)多個(gè)線程同時(shí)嘗試占用同一個(gè)鎖時(shí)晨雳,CAS操作只能保證一個(gè)線程操作成功,剩下的就去乖乖排隊(duì)了就斤。

"非公平"即提現(xiàn)在這里悍募,如果占用鎖的線程剛釋放鎖,state置為0洋机,而排隊(duì)等待的線程還未被喚醒诞外,新來的線程直接搶了該鎖桩引,那么就相當(dāng)于插隊(duì)了。
若當(dāng)前有三個(gè)線程去競(jìng)爭(zhēng)鎖,假設(shè)線程A的CAS操作成功了拾因,拿到了鎖開開心心的返回了,那么線程B和C則設(shè)置state失敗若专,走到else中脖含。
NonfairSync.lock()
  --> sync.lock();
         // 調(diào)用的是NonfairSync.lock()
    -->  final void lock() { @NonfairSync
            // CAS操作,判斷state是否為0(表示當(dāng)前鎖未被占用)角骤,如果是0隅忿,則把它置為1心剥,并且設(shè)置當(dāng)前線程為該鎖的獨(dú)占線程,表示獲取鎖成功
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                // 獲取鎖失敗
                acquire(1);
                -->  public final void acquire(int arg) { @AbstractQueuedSynchronizer
                        // 再次嘗試去獲取鎖背桐。細(xì)節(jié)看下面①优烧。。
                        if (!tryAcquire(arg) 
                           // 如果上面嘗試獲取鎖失敗链峭,addWaiter 是入隊(duì)操作畦娄。流程看下面②。弊仪。熙卡。。励饵。驳癌。。曲横。
                           // acquireQueued 是掛起操作喂柒。流程看下面③。禾嫉。灾杰。。熙参。艳吠。。孽椰。
                           && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                        selfInterrupt();
                     }
        }

①:嘗試去獲取鎖昭娩。如果嘗試獲取成功,方法直接返回黍匾。

非公平鎖tryAcquire的流程是:檢查state狀態(tài)栏渺,若為0,表示鎖未被占用锐涯,那么嘗試占用磕诊。若不為0,檢查當(dāng)前鎖是否被自己占用纹腌,若被自己占用霎终,則更新state字段,表示重入鎖的次數(shù)升薯。如果都沒有成功莱褒,則獲取鎖失敗,返回false涎劈。

protected final boolean tryAcquire(int acquires) { @NonfairSync
   // 嘗試非公平鎖广凸,調(diào)用的是 Sync.nonfairTryAcquire()
   return nonfairTryAcquire(acquires);
   -->   final boolean nonfairTryAcquire(int acquires) { @Sync
            // 獲取當(dāng)前線程
            final Thread current = Thread.currentThread();
            // 獲取state值
            int c = getState();
            // state == 0 ,表示沒有線程占用鎖
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    // 占用鎖成功阅茶,設(shè)置獨(dú)占鎖線程為當(dāng)前線程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 如果當(dāng)前線程已經(jīng)占用該鎖了。
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                // 更新state值為新的重入次數(shù)
                setState(nextc);
                return true;
            }
            return false;
        }
}

②:嘗試獲取鎖失敗炮障,那就要將當(dāng)前線程入隊(duì)目派。

    // 將新節(jié)點(diǎn)和當(dāng)前線程關(guān)聯(lián),并入隊(duì)
    private Node addWaiter(Node mode) {
        // 初始化節(jié)點(diǎn)胁赢,設(shè)置關(guān)聯(lián)線程和模式(獨(dú)占 or 共享)
        Node node = new Node(mode);
        
        // for循環(huán),直到入隊(duì)成功返回
        for (;;) {
            // 獲取尾節(jié)點(diǎn)引用
            Node oldTail = tail;
            // 尾節(jié)點(diǎn)不為空白筹,說明隊(duì)列已經(jīng)初始化過了
            if (oldTail != null) {
                U.putObject(node, Node.PREV, oldTail);
                // 設(shè)置新節(jié)點(diǎn)為尾節(jié)點(diǎn)
                if (compareAndSetTail(oldTail, node)) {
                    oldTail.next = node;
                    return node;
                }
            } else {
                // 尾節(jié)點(diǎn)為空智末,說明隊(duì)列還未初始化,需要初始化head節(jié)點(diǎn)并入隊(duì)新節(jié)點(diǎn)
                initializeSyncQueue();
                     // 在第一次爭(zhēng)用時(shí)初始化頭部和尾部字段
                -->  private final void initializeSyncQueue() {
                        Node h;
                        if (U.compareAndSwapObject(this, HEAD, null, (h = new Node())))
                           tail = h;
                     }
            }
        }
    }

這里體現(xiàn)了經(jīng)典的自旋+CAS組合來實(shí)現(xiàn)非阻塞的原子操作徒河。由于initializeSyncQueue的實(shí)現(xiàn)使用CAS操作系馆,所以只有一個(gè)線程會(huì)創(chuàng)建head節(jié)點(diǎn)成功。

③:入隊(duì)后進(jìn)行掛起顽照。這個(gè)方法讓已經(jīng)入隊(duì)的線程嘗試獲取鎖由蘑,若失敗則會(huì)被掛起

    // 已經(jīng)入隊(duì)的線程嘗試獲取鎖
    final boolean acquireQueued(final Node node, int arg) {
        try {
            // 標(biāo)記線程是否被中斷過
            boolean interrupted = false;
            for (;;) {
                // 獲取當(dāng)前節(jié)點(diǎn)的前面一個(gè)節(jié)點(diǎn)
                final Node p = node.predecessor();
                // 如果前面一個(gè)節(jié)點(diǎn)是是head,則該節(jié)點(diǎn)排第二代兵。便有資格去嘗試獲取鎖
                if (p == head && tryAcquire(arg)) {
                    // 獲取成功尼酿,將當(dāng)前節(jié)點(diǎn)設(shè)置為head節(jié)點(diǎn)
                    setHead(node);
                    // 原h(huán)ead節(jié)點(diǎn)出隊(duì),在某個(gè)時(shí)間點(diǎn)被gc回收
                    p.next = null; // help GC
                    // 返回是否被中斷過
                    return interrupted;
                }
                // 判斷獲取失敗后是否可以掛起植影,若可以則掛起裳擎。接著看下面的④。思币。鹿响。。谷饿。惶我。。博投。绸贡。
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    // 線程若被中斷,設(shè)置interrupted為true
                    interrupted = true;
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        }
    }

④:判斷當(dāng)前線程獲取鎖失敗之后是否需要掛起

    // 檢查并更新未能獲取的節(jié)點(diǎn)的狀態(tài)贬堵。如果線程應(yīng)該阻塞恃轩,則返回 true。這是所有采集循環(huán)中的主要信號(hào)控制黎做。要求 pred == node.prev叉跛。
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        // 前面一個(gè)節(jié)點(diǎn)的狀態(tài)
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            // 前面一個(gè)節(jié)點(diǎn)的狀態(tài)為signal,返回true
            return true;
        // 前面一個(gè)節(jié)點(diǎn)的狀態(tài)為cancelled蒸殿,返回true
        if (ws > 0) {
            // 從隊(duì)尾向前尋找第一個(gè)狀態(tài)不為cancelled的節(jié)點(diǎn)
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            // 將前驅(qū)節(jié)點(diǎn)的狀態(tài)設(shè)置為signal
            pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
        }
        return false;
    }


  // 掛起當(dāng)前線程,返回線程中斷狀態(tài)并重置
  private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
  }

線程入隊(duì)后能過掛起的前提是筷厘,它的前驅(qū)節(jié)點(diǎn)的狀態(tài)是signal鸣峭,它的含義是,提醒前面的節(jié)點(diǎn)酥艳,如果前面的節(jié)點(diǎn)獲取到鎖并出隊(duì)后摊溶,把自己?jiǎn)拘选K詓houldParkAfterFailedAcquire會(huì)先判斷當(dāng)前節(jié)點(diǎn)的前驅(qū)是否狀態(tài)符合要求充石,如符合則返回true莫换,然后調(diào)用parkAndCheckInterrupt,將自己掛起骤铃。如果不符合拉岁,再看前驅(qū)節(jié)點(diǎn)是否大于0(canceled),若是那么向前遍歷直到找到第一個(gè)符合條件的前驅(qū)惰爬,若不是則將前驅(qū)節(jié)點(diǎn)的狀態(tài)設(shè)置為singal喊暖。

NonfairSync.unlock()
// 嘗試釋放此鎖。如果當(dāng)前線程是這個(gè)鎖的持有者撕瞧,那么持有計(jì)數(shù)就會(huì)遞減陵叽。如果保持計(jì)數(shù)現(xiàn)在為零,則釋放鎖丛版。
public void unlock() {
   sync.release(1);
   -->  public final boolean release(int arg) { @AbstractQueuedSynchronizer
        // 調(diào)用 Sync.tryRelease()
        // 嘗試釋放鎖巩掺,如果釋放成功,那么查看頭節(jié)點(diǎn)的狀態(tài)是否是signal硼婿,如果是則喚醒頭節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)關(guān)聯(lián)的線程锌半,如果釋放失敗,那么返回false寇漫。表示解鎖失敗刊殉。
        // 具體細(xì)節(jié),看下面
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
}

嘗試釋放當(dāng)前線程占用的鎖

   protected final boolean tryRelease(int releases) {
        // 計(jì)算釋放后state值
        int c = getState() - releases;
        // 如果不是當(dāng)前線程占用鎖州胳,那么拋出異常
        if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
        boolean free = false;
        if (c == 0) {
            // 鎖被重入次數(shù)為0记焊,表示釋放成功
            free = true;
            // 清空獨(dú)占線程
            setExclusiveOwnerThread(null);
        }
        // 更新state值
        setState(c);
        return free;
   }

入?yún)⑹?。tryRelease的過程是:當(dāng)前釋放鎖的線程若不持有鎖栓撞,那就拋出異常遍膜。若持有鎖,計(jì)算釋放后的state是否為0瓤湘,若為0則表示鎖已經(jīng)被成功釋放瓢颅,并且清空獨(dú)占線程。最后更新state值弛说,返回free挽懦。

FairSync

公平鎖和非公平鎖的不同之處在于,公平鎖在獲取鎖的時(shí)候木人,不會(huì)先去檢查state的狀態(tài)信柿,而是直接執(zhí)行

final void lock() {
    acquire(1);
}
超時(shí)機(jī)制

在ReentrantLock的tryLock(long timeout, TimeUnit unit)提供了超時(shí)獲取鎖的功能冀偶。如果在指定時(shí)間內(nèi)獲取到了鎖就返回true,如果沒有就返回false渔嚷。這種機(jī)制避免了線程無限期等待鎖的釋放进鸠。

public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

還是看一下具體實(shí)現(xiàn)

public boolean tryLock(long timeout, TimeUnit unit) @ReentrantLock
       throws InterruptedException {
   return sync.tryAcquireNanos(1, unit.toNanos(timeout));
   -->  public final boolean tryAcquireNanos(int arg, long nanosTimeout) @AbstractQueuedSynchronizer
          throws InterruptedException {
        // 如果線程被中斷了,拋出異常
        if (Thread.interrupted())
            throw new InterruptedException();
        // 先嘗試獲取鎖形病,獲取成功就直接返回客年。獲取失敗則進(jìn)入doAcquireNanos。下面看下doAcquireNanos()
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }
}

在有限的時(shí)間內(nèi)去競(jìng)爭(zhēng)鎖

    private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        // 如果等待時(shí)間小于0窒朋,直接返回獲取鎖失敗
        if (nanosTimeout <= 0L)
            return false;
        // 計(jì)算最后嘗試獲取鎖的時(shí)間點(diǎn)
        final long deadline = System.nanoTime() + nanosTimeout;
        // 線程入隊(duì)
        final Node node = addWaiter(Node.EXCLUSIVE);
        try {
            // 又是自旋
            for (;;) {
                // 獲取前驅(qū)節(jié)點(diǎn)
                final Node p = node.predecessor();
                // 如果前驅(qū)節(jié)點(diǎn)是頭節(jié)點(diǎn)搀罢,并且獲取鎖成功,則將當(dāng)前節(jié)點(diǎn)變成頭節(jié)點(diǎn)
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return true;
                }
                // 計(jì)算嘗試獲取鎖的剩余時(shí)長(zhǎng)侥猩。
                nanosTimeout = deadline - System.nanoTime();
                // 如果已經(jīng)超時(shí),則取消獲取抵赢。返回false
                if (nanosTimeout <= 0L) {
                    cancelAcquire(node);
                    return false;
                }

                // 超時(shí)時(shí)間未到欺劳,且需要掛起
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD)
                    // 阻塞當(dāng)前線程知道超時(shí)時(shí)間到期
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        }
    }

doAcquireNanos的流程簡(jiǎn)述為:線程先入隊(duì)等待,然后開始自旋铅鲤,嘗試獲取鎖划提,獲取成功就返回,失敗則在隊(duì)列中找一個(gè)安全點(diǎn)把自己掛起直到超時(shí)時(shí)間過期邢享。這里為什么還需要循環(huán)呢鹏往?因?yàn)楫?dāng)前線程節(jié)點(diǎn)的前驅(qū)狀態(tài)可能不是signal,那么在當(dāng)前這一輪循環(huán)中線程不會(huì)被掛起骇塘,然后更 新超時(shí)時(shí)間伊履,開始新一輪的嘗試。

3.3 ReadWriteLock讀寫鎖

3.3.1 ReadWriteLock接口

public interface ReadWriteLock {
    // 返回讀鎖款违。
    Lock readLock();

    // 返回寫鎖
    Lock writeLock();
}

提供了讀和寫兩個(gè)操作唐瀑,兩個(gè)鎖的存在,是為了將讀和寫分開來操作插爹。為了提高效率哄辣,多個(gè)線程可以同時(shí)進(jìn)行讀的操作,但寫鎖是獨(dú)占的赠尾。而讀和寫又不能同時(shí)進(jìn)行力穗。

3.3.2 ReentrantReadWriteLock可重入讀寫鎖

ReentrantReadWriteLock是ReadWriteLock接口的唯一實(shí)例。同時(shí)提供了很多操作方法气嫁。

讀寫鎖代碼示例:

// 讀
class StartReadRunnable implements Runnable {
    @Override
    public void run() {
        readLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + ":開始讀当窗。。杉编。超全。咆霜。" + DateUtils.INSTANCE.getCurrDataStr());
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + ":結(jié)束讀。嘶朱。蛾坯。。疏遏。" + DateUtils.INSTANCE.getCurrDataStr());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            readLock.unlock();
        }
    }
}

// 寫
static class StartWriteRunnable implements Runnable {
    @Override
    public void run() {
        writeLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + ":開始寫脉课。。财异。倘零。。" + DateUtils.INSTANCE.getCurrDataStr());
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + ":結(jié)束寫戳寸。呈驶。。疫鹊。袖瞻。" + DateUtils.INSTANCE.getCurrDataStr());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            writeLock.unlock();
        }
    }
}
class ReadAndWriteLockDemo {
    static ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock();
    static Lock readLock = mReadWriteLock.readLock();
    static Lock writeLock = mReadWriteLock.writeLock();

    public static void main(String[] args) {
        // 創(chuàng)建8個(gè)線程,2個(gè)線程寫拆吆,6個(gè)線程讀聋迎。
        ExecutorService executorService = Executors.newFixedThreadPool(8);
        for (int i = 0; i < 4; i++) {
            if (i % 2 == 0) {
                executorService.execute(new StartReadRunnable());
            } else {
                executorService.execute(new StartWriteRunnable());
            }
        }

        for (int i = 0; i < 4; i++) {
            executorService.execute(new StartReadRunnable());
        }
    }

打印日志:

pool-1-thread-1:開始讀。枣耀。霉晕。。捞奕。22/04/08 14:15:30
pool-1-thread-1:結(jié)束讀牺堰。。缝彬。萌焰。。22/04/08 14:15:32
pool-1-thread-2:開始寫谷浅。扒俯。。一疯。撼玄。22/04/08 14:15:32 // 線程1讀完,線程2才能接著寫
pool-1-thread-2:結(jié)束寫墩邀。掌猛。。。荔茬。22/04/08 14:15:34
pool-1-thread-4:開始寫废膘。。慕蔚。丐黄。。22/04/08 14:15:34 // 線程2寫完孔飒,線程4才能接著寫
pool-1-thread-4:結(jié)束寫灌闺。。坏瞄。桂对。。22/04/08 14:15:36
pool-1-thread-3:開始讀鸠匀。蕉斜。。缀棍。蛛勉。22/04/08 14:15:36 // 線程4寫完,線程3才能接著寫
pool-1-thread-5:開始讀睦柴。。毡熏。坦敌。。22/04/08 14:15:36 
pool-1-thread-6:開始讀痢法。狱窘。。财搁。蘸炸。22/04/08 14:15:36 
pool-1-thread-8:開始讀。尖奔。搭儒。。提茁。22/04/08 14:15:36 
pool-1-thread-7:開始讀淹禾。。茴扁。铃岔。。22/04/08 14:15:36 
pool-1-thread-3:結(jié)束讀峭火。毁习。智嚷。。纺且。22/04/08 14:15:38
pool-1-thread-5:結(jié)束讀盏道。。隆檀。摇天。。22/04/08 14:15:38
pool-1-thread-8:結(jié)束讀恐仑。泉坐。。裳仆。腕让。22/04/08 14:15:38
pool-1-thread-6:結(jié)束讀。歧斟。纯丸。。静袖。22/04/08 14:15:38
pool-1-thread-7:結(jié)束讀觉鼻。。队橙。坠陈。。22/04/08 14:15:38 // 線程3捐康、4仇矾、5、6解总、7贮匕、8可以同時(shí)讀

3.4 公平鎖

公平鎖即當(dāng)多個(gè)線程等待同一個(gè)鎖的時(shí)候,當(dāng)鎖被釋放了花枫,并不是多個(gè)線程隨機(jī)獲取到鎖刻盐,而是被等待時(shí)間最長(zhǎng)的線程獲取到。synchronized是一個(gè)非公平鎖乌昔,無法保證獲取到鎖的先后順序隙疚。ReentrantLock和ReentrantReadWriteLock默認(rèn)也是非公平鎖,但可以在構(gòu)造方法中傳入一個(gè)boolean值磕道,來標(biāo)識(shí)是否是公平的供屉。

// 創(chuàng)建ReentrantLock的實(shí)例。這相當(dāng)于使用ReentrantLock(false) 
public ReentrantLock() {
    sync = new NonfairSync(); // 非公平鎖
}

// 使用給定的公平策略創(chuàng)建ReentrantLock的實(shí)例。
// 參數(shù):fair – 如果此鎖應(yīng)使用公平排序策略伶丐,則為true
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

參考上面的讀寫實(shí)例悼做,可以看到線程并不是按順序執(zhí)行的。如果我們?cè)赗eentrantReadWriteLock構(gòu)造方法中傳入true哗魂,則打印結(jié)果如下:

pool-1-thread-1:開始讀肛走。。录别。朽色。。22/04/08 14:28:16
pool-1-thread-1:結(jié)束讀组题。葫男。。崔列。梢褐。22/04/08 14:28:18
pool-1-thread-2:開始寫。赵讯。盈咳。。边翼。22/04/08 14:28:18
pool-1-thread-2:結(jié)束寫鱼响。。组底。热押。。22/04/08 14:28:20
pool-1-thread-3:開始讀斤寇。。拥褂。娘锁。。22/04/08 14:28:20
pool-1-thread-3:結(jié)束讀饺鹃。莫秆。。悔详。镊屎。22/04/08 14:28:22
pool-1-thread-4:開始寫。茄螃。缝驳。。。22/04/08 14:28:22
pool-1-thread-4:結(jié)束寫用狱。运怖。。夏伊。摇展。22/04/08 14:28:24
pool-1-thread-5:開始讀。溺忧。咏连。。鲁森。22/04/08 14:28:24
pool-1-thread-6:開始讀祟滴。。刀森。踱启。。22/04/08 14:28:24
pool-1-thread-7:開始讀研底。埠偿。。榜晦。冠蒋。22/04/08 14:28:24
pool-1-thread-8:開始讀。乾胶。抖剿。。识窿。22/04/08 14:28:24
pool-1-thread-6:結(jié)束讀斩郎。。喻频。缩宜。。22/04/08 14:28:26
pool-1-thread-7:結(jié)束讀甥温。锻煌。。姻蚓。宋梧。22/04/08 14:28:26
pool-1-thread-5:結(jié)束讀。狰挡。捂龄。释涛。。22/04/08 14:28:26
pool-1-thread-8:結(jié)束讀跺讯。枢贿。。刀脏。局荚。22/04/08 14:28:26

3.5 Lock和synchronized的選擇

  • synchronized是內(nèi)置語(yǔ)言實(shí)現(xiàn)的關(guān)鍵字,Lock是為了實(shí)現(xiàn)更高級(jí)鎖功能而提供的接口愈污;
  • Lock實(shí)現(xiàn)了tryLock等接口耀态,線程未獲取到鎖時(shí)不用一直等待;
  • synchronized發(fā)生異常時(shí)自動(dòng)釋放占有的鎖暂雹,Lock需要在finally塊中手動(dòng)釋放鎖首装。
  • Lock可以通過Lock.lockInterruptibly()來中斷鎖;
  • 由于Lock提供了時(shí)間限制同步杭跪,可被打斷同步等機(jī)制仙逻,線程激烈競(jìng)爭(zhēng)時(shí)Lock的性能遠(yuǎn)優(yōu)于synchronized,即有大量線程時(shí)推薦使用Lock
  • ReentrantReadWriteLock實(shí)現(xiàn)了封裝好的讀寫鎖用于大量讀少量寫的場(chǎng)景涧尿。解決了synchronized難以讀寫同步的問題系奉。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市姑廉,隨后出現(xiàn)的幾起案子缺亮,更是在濱河造成了極大的恐慌,老刑警劉巖桥言,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件萌踱,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡号阿,警方通過查閱死者的電腦和手機(jī)并鸵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扔涧,“玉大人能真,你說我怎么就攤上這事∪拍” “怎么了?”我有些...
    開封第一講書人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵疼约,是天一觀的道長(zhǎng)卤档。 經(jīng)常有香客問我,道長(zhǎng)程剥,這世上最難降的妖魔是什么劝枣? 我笑而不...
    開封第一講書人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任汤踏,我火速辦了婚禮,結(jié)果婚禮上舔腾,老公的妹妹穿的比我還像新娘溪胶。我一直安慰自己稳诚,他們只是感情好哗脖,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著才避,像睡著了一般。 火紅的嫁衣襯著肌膚如雪氨距。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,829評(píng)論 1 290
  • 那天俏让,我揣著相機(jī)與錄音楞遏,去河邊找鬼。 笑死首昔,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沙廉。 我是一名探鬼主播拘荡,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼撬陵,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼珊皿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起巨税,我...
    開封第一講書人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤蟋定,失蹤者是張志新(化名)和其女友劉穎草添,沒想到半個(gè)月后驶兜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抄淑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了肆资。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡灶芝,死狀恐怖唉韭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情犯犁,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布酸役,位于F島的核電站住诸,受9級(jí)特大地震影響簇捍,放射性物質(zhì)發(fā)生泄漏只壳。R本人自食惡果不足惜暑塑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一吼句、第九天 我趴在偏房一處隱蔽的房頂上張望事格。 院中可真熱鬧惕艳,春花似錦、人聲如沸远搪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至劫瞳,卻和暖如春倘潜,著一層夾襖步出監(jiān)牢的瞬間志于,已是汗流浹背涮因。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工伺绽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留养泡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓澜掩,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親杖挣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

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