synchronized簡介

1. synchronized簡介

在學(xué)習(xí)知識前,我們先來看一個(gè)現(xiàn)象:

public class SynchronizedDemo implements Runnable {
    private static int count = 0;

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new SynchronizedDemo());
            thread.start();
        }
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("result: " + count);
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000000; I++)
            count++;
    }
}

開啟了10個(gè)線程,每個(gè)線程都累加了1000000次,如果結(jié)果正確的話自然而然總數(shù)就應(yīng)該是10 * 1000000 = 10000000。可就運(yùn)行多次結(jié)果都不是這個(gè)數(shù),而且每次運(yùn)行結(jié)果都不一樣。這是為什么了?有什么解決方案了?這就是我們今天要聊的事情。

在上一篇博文中我們已經(jīng)了解了java內(nèi)存模型的一些知識,并且已經(jīng)知道出現(xiàn)線程安全的主要來源于JMM的設(shè)計(jì),主要集中在主內(nèi)存和線程的工作內(nèi)存而導(dǎo)致的內(nèi)存可見性問題,以及重排序?qū)е碌膯栴},進(jìn)一步知道了happens-before規(guī)則。線程運(yùn)行時(shí)擁有自己的棧空間,會在自己的棧空間運(yùn)行,如果多線程間沒有共享的數(shù)據(jù)也就是說多線程間并沒有協(xié)作完成一件事情,那么,多線程就不能發(fā)揮優(yōu)勢,不能帶來巨大的價(jià)值。那么共享數(shù)據(jù)的線程安全問題怎樣處理?很自然而然的想法就是每一個(gè)線程依次去讀寫這個(gè)共享變量,這樣就不會有任何數(shù)據(jù)安全的問題,因?yàn)槊總€(gè)線程所操作的都是當(dāng)前最新的版本數(shù)據(jù)。那么,在java關(guān)鍵字synchronized就具有使每個(gè)線程依次排隊(duì)操作共享變量的功能。很顯然,這種同步機(jī)制效率很低,但synchronized是其他并發(fā)容器實(shí)現(xiàn)的基礎(chǔ),對它的理解也會大大提升對并發(fā)編程的感覺,從功利的角度來說,這也是面試高頻的考點(diǎn)。好了,下面,就來具體說說這個(gè)關(guān)鍵字。

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

在java代碼中使用synchronized可是使用在代碼塊和方法中,根據(jù)Synchronized用的位置可以有這些使用場景:

image.png

如圖,synchronized可以用在方法上也可以使用在代碼塊中,其中方法是實(shí)例方法和靜態(tài)方法分別鎖的是該類的實(shí)例對象和該類的對象。而使用在代碼塊中也可以分為三種,具體的可以看上面的表格。這里的需要注意的是:如果鎖的是類對象的話,盡管new多個(gè)實(shí)例對象,但他們?nèi)匀皇菍儆谕粋€(gè)類依然會被鎖住,即線程之間保證同步關(guān)系

現(xiàn)在我們已經(jīng)知道了怎樣synchronized了,看起來很簡單,擁有了這個(gè)關(guān)鍵字就真的可以在并發(fā)編程中得心應(yīng)手了嗎?愛學(xué)的你,就真的不想知道synchronized底層是怎樣實(shí)現(xiàn)了嗎?

2.1 對象鎖(monitor)機(jī)制

現(xiàn)在我們來看看synchronized的具體底層實(shí)現(xiàn)。先寫一個(gè)簡單的demo:

public class SynchronizedDemo {
    public static void main(String[] args) {
        synchronized (SynchronizedDemo.class) {
        }
        method();
    }

    private static void method() {
    }
}

上面的代碼中有一個(gè)同步代碼塊,鎖住的是類對象,并且還有一個(gè)同步靜態(tài)方法,鎖住的依然是該類的類對象。編譯之后,切換到SynchronizedDemo.class的同級目錄之后,然后用javap -v SynchronizedDemo.class查看字節(jié)碼文件:

image.png

如圖,上面用黃色高亮的部分就是需要注意的部分了,這也是添Synchronized關(guān)鍵字之后獨(dú)有的。執(zhí)行同步代碼塊后首先要先執(zhí)行monitorenter指令,退出的時(shí)候monitorexit指令。通過分析之后可以看出,使用Synchronized進(jìn)行同步,其關(guān)鍵就是必須要對對象的監(jiān)視器monitor進(jìn)行獲取,當(dāng)線程獲取monitor后才能繼續(xù)往下執(zhí)行,否則就只能等待。而這個(gè)獲取的過程是互斥的,即同一時(shí)刻只有一個(gè)線程能夠獲取到monitor。上面的demo中在執(zhí)行完同步代碼塊之后緊接著再會去執(zhí)行一個(gè)靜態(tài)同步方法,而這個(gè)方法鎖的對象依然就這個(gè)類對象,那么這個(gè)正在執(zhí)行的線程還需要獲取該鎖嗎?答案是不必的,從上圖中就可以看出來,執(zhí)行靜態(tài)同步方法的時(shí)候就只有一條monitorexit指令,并沒有monitorenter獲取鎖的指令。這就是鎖的重入性,即在同一鎖程中,線程不需要再次獲取同一把鎖。Synchronized先天具有重入性。每個(gè)對象擁有一個(gè)計(jì)數(shù)器,當(dāng)線程獲取該對象鎖后,計(jì)數(shù)器就會加一,釋放鎖后就會將計(jì)數(shù)器減一

任意一個(gè)對象都擁有自己的監(jiān)視器,當(dāng)這個(gè)對象由同步塊或者這個(gè)對象的同步方法調(diào)用時(shí),執(zhí)行方法的線程必須先獲取該對象的監(jiān)視器才能進(jìn)入同步塊和同步方法,如果沒有獲取到監(jiān)視器的線程將會被阻塞在同步塊和同步方法的入口處,進(jìn)入到BLOCKED狀態(tài)(關(guān)于線程的狀態(tài)可以看這篇文章

下圖表現(xiàn)了對象,對象監(jiān)視器,同步隊(duì)列以及執(zhí)行線程狀態(tài)之間的關(guān)系:

image.png

該圖可以看出,任意線程對Object的訪問,首先要獲得Object的監(jiān)視器,如果獲取失敗,該線程就進(jìn)入同步狀態(tài),線程狀態(tài)變?yōu)锽LOCKED,當(dāng)Object的監(jiān)視器占有者釋放后,在同步隊(duì)列中得線程就會有機(jī)會重新獲取該監(jiān)視器。

2.2 synchronized的happens-before關(guān)系

在上一篇文章中討論過happens-before規(guī)則,抱著學(xué)以致用的原則我們現(xiàn)在來看一看Synchronized的happens-before規(guī)則,即監(jiān)視器鎖規(guī)則:對同一個(gè)監(jiān)視器的解鎖,happens-before于對該監(jiān)視器的加鎖。繼續(xù)來看代碼:

public class MonitorDemo {
    private int a = 0;

    public synchronized void writer() {     // 1
        a++;                                // 2
    }                                       // 3

    public synchronized void reader() {    // 4
        int i = a;                         // 5
    }                                      // 6
}

該代碼的happens-before關(guān)系如圖所示:

image.png

在圖中每一個(gè)箭頭連接的兩個(gè)節(jié)點(diǎn)就代表之間的happens-before關(guān)系,黑色的是通過程序順序規(guī)則推導(dǎo)出來,紅色的為監(jiān)視器鎖規(guī)則推導(dǎo)而出:線程A釋放鎖happens-before線程B加鎖,藍(lán)色的則是通過程序順序規(guī)則和監(jiān)視器鎖規(guī)則推測出來happens-befor關(guān)系,通過傳遞性規(guī)則進(jìn)一步推導(dǎo)的happens-before關(guān)系。現(xiàn)在我們來重點(diǎn)關(guān)注2 happens-before 5,通過這個(gè)關(guān)系我們可以得出什么?

根據(jù)happens-before的定義中的一條:如果A happens-before B,則A的執(zhí)行結(jié)果對B可見,并且A的執(zhí)行順序先于B。線程A先對共享變量A進(jìn)行加一,由2 happens-before 5關(guān)系可知線程A的執(zhí)行結(jié)果對線程B可見即線程B所讀取到的a的值為1。

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

在上一篇文章提到過JMM核心為兩個(gè)部分:happens-before規(guī)則以及內(nèi)存抽象模型。我們分析完Synchronized的happens-before關(guān)系后,還是不太完整的,我們接下來看看基于java內(nèi)存抽象模型的Synchronized的內(nèi)存語義。

廢話不多說依舊先上圖。

image.png

從上圖可以看出,線程A會首先先從主內(nèi)存中讀取共享變量a=0的值然后將該變量拷貝到自己的本地內(nèi)存,進(jìn)行加一操作后,再將該值刷新到主內(nèi)存,整個(gè)過程即為線程A 加鎖-->執(zhí)行臨界區(qū)代碼-->釋放鎖相對應(yīng)的內(nèi)存語義。

image.png

線程B獲取鎖的時(shí)候同樣會從主內(nèi)存中共享變量a的值,這個(gè)時(shí)候就是最新的值1,然后將該值拷貝到線程B的工作內(nèi)存中去,釋放鎖的時(shí)候同樣會重寫到主內(nèi)存中。

從整體上來看,線程A的執(zhí)行結(jié)果(a=1)對線程B是可見的,實(shí)現(xiàn)原理為:釋放鎖的時(shí)候會將值刷新到主內(nèi)存中,其他線程獲取鎖時(shí)會強(qiáng)制從主內(nèi)存中獲取最新的值。另外也驗(yàn)證了2 happens-before 5,2的執(zhí)行結(jié)果對5是可見的。

從橫向來看,這就像線程A通過主內(nèi)存中的共享變量和線程B進(jìn)行通信,A 告訴 B 我們倆的共享數(shù)據(jù)現(xiàn)在為1啦,這種線程間的通信機(jī)制正好吻合java的內(nèi)存模型正好是共享內(nèi)存的并發(fā)模型結(jié)構(gòu)。

3. synchronized優(yōu)化

通過上面的討論現(xiàn)在我們對Synchronized應(yīng)該有所印象了,它最大的特征就是在同一時(shí)刻只有一個(gè)線程能夠獲得對象的監(jiān)視器(monitor),從而進(jìn)入到同步代碼塊或者同步方法之中,即表現(xiàn)為互斥性(排它性)。這種方式肯定效率低下,每次只能通過一個(gè)線程,既然每次只能通過一個(gè),這種形式不能改變的話,那么我們能不能讓每次通過的速度變快一點(diǎn)了。打個(gè)比方,去收銀臺付款,之前的方式是,大家都去排隊(duì),然后去紙幣付款收銀員找零,有的時(shí)候付款的時(shí)候在包里拿出錢包再去拿出錢,這個(gè)過程是比較耗時(shí)的,然后,支付寶解放了大家去錢包找錢的過程,現(xiàn)在只需要掃描下就可以完成付款了,也省去了收銀員跟你找零的時(shí)間的了。同樣是需要排隊(duì),但整個(gè)付款的時(shí)間大大縮短,是不是整體的效率變高速率變快了?這種優(yōu)化方式同樣可以引申到鎖優(yōu)化上,縮短獲取鎖的時(shí)間,偉大的科學(xué)家們也是這樣做的,令人欽佩,畢竟java是這么優(yōu)秀的語言(微笑臉)。

在聊到鎖的優(yōu)化也就是鎖的幾種狀態(tài)前,有兩個(gè)知識點(diǎn)需要先關(guān)注:(1)CAS操作 (2)Java對象頭,這是理解下面知識的前提條件。

3.1 CAS操作

3.1.1 什么是CAS?

使用鎖時(shí),線程獲取鎖是一種悲觀鎖策略,即假設(shè)每一次執(zhí)行臨界區(qū)代碼都會產(chǎn)生沖突,所以當(dāng)前線程獲取到鎖的時(shí)候同時(shí)也會阻塞其他線程獲取該鎖。而CAS操作(又稱為無鎖操作)是一種樂觀鎖策略,它假設(shè)所有線程訪問共享資源的時(shí)候不會出現(xiàn)沖突,既然不會出現(xiàn)沖突自然而然就不會阻塞其他線程的操作。因此,線程就不會出現(xiàn)阻塞停頓的狀態(tài)。那么,如果出現(xiàn)沖突了怎么辦?無鎖操作是使用CAS(compare and swap)又叫做比較交換來鑒別線程是否出現(xiàn)沖突,出現(xiàn)沖突就重試當(dāng)前操作直到?jīng)]有沖突為止。

3.1.2 CAS的操作過程

CAS比較交換的過程可以通俗的理解為CAS(V,O,N),包含三個(gè)值分別為:V 內(nèi)存地址存放的實(shí)際值;O 預(yù)期的值(舊值);N 更新的新值。當(dāng)V和O相同時(shí),也就是說舊值和內(nèi)存中實(shí)際的值相同表明該值沒有被其他線程更改過,即該舊值O就是目前來說最新的值了,自然而然可以將新值N賦值給V。反之,V和O不相同,表明該值已經(jīng)被其他線程改過了則該舊值O不是最新版本的值了,所以不能將新值N賦給V,返回V即可。當(dāng)多個(gè)線程使用CAS操作一個(gè)變量是,只有一個(gè)線程會成功,并成功更新,其余會失敗。失敗的線程會重新嘗試,當(dāng)然也可以選擇掛起線程

CAS的實(shí)現(xiàn)需要硬件指令集的支撐,在JDK1.5后虛擬機(jī)才可以使用處理器提供的CMPXCHG指令實(shí)現(xiàn)。

Synchronized VS CAS

元老級的Synchronized(未優(yōu)化前)最主要的問題是:在存在線程競爭的情況下會出現(xiàn)線程阻塞和喚醒鎖帶來的性能問題,因?yàn)檫@是一種互斥同步(阻塞同步)。而CAS并不是武斷的間線程掛起,當(dāng)CAS操作失敗后會進(jìn)行一定的嘗試,而非進(jìn)行耗時(shí)的掛起喚醒的操作,因此也叫做非阻塞同步。這是兩者主要的區(qū)別。

3.1.3 CAS的應(yīng)用場景

在J.U.C包中利用CAS實(shí)現(xiàn)類有很多,可以說是支撐起整個(gè)concurrency包的實(shí)現(xiàn),在Lock實(shí)現(xiàn)中會有CAS改變state變量,在atomic包中的實(shí)現(xiàn)類也幾乎都是用CAS實(shí)現(xiàn),關(guān)于這些具體的實(shí)現(xiàn)場景在之后會詳細(xì)聊聊,現(xiàn)在有個(gè)印象就好了(微笑臉)。

3.1.4 CAS的問題

1. ABA問題
因?yàn)镃AS會檢查舊值有沒有變化,這里存在這樣一個(gè)有意思的問題。比如一個(gè)舊值A(chǔ)變?yōu)榱顺葿,然后再變成A,剛好在做CAS時(shí)檢查發(fā)現(xiàn)舊值并沒有變化依然為A,但是實(shí)際上的確發(fā)生了變化。解決方案可以沿襲數(shù)據(jù)庫中常用的樂觀鎖方式,添加一個(gè)版本號可以解決。原來的變化路徑A->B->A就變成了1A->2B->3C。java這么優(yōu)秀的語言,當(dāng)然在java 1.5后的atomic包中提供了AtomicStampedReference來解決ABA問題,解決思路就是這樣的。

2. 自旋時(shí)間過長

使用CAS時(shí)非阻塞同步,也就是說不會將線程掛起,會自旋(無非就是一個(gè)死循環(huán))進(jìn)行下一次嘗試,如果這里自旋時(shí)間過長對性能是很大的消耗。如果JVM能支持處理器提供的pause指令,那么在效率上會有一定的提升。

3. 只能保證一個(gè)共享變量的原子操作

當(dāng)對一個(gè)共享變量執(zhí)行操作時(shí)CAS能保證其原子性,如果對多個(gè)共享變量進(jìn)行操作,CAS就不能保證其原子性。有一個(gè)解決方案是利用對象整合多個(gè)共享變量,即一個(gè)類中的成員變量就是這幾個(gè)共享變量。然后將這個(gè)對象做CAS操作就可以保證其原子性。atomic中提供了AtomicReference來保證引用對象之間的原子性。

3.2 Java對象頭

在同步的時(shí)候是獲取對象的monitor,即獲取到對象的鎖。那么對象的鎖怎么理解?無非就是類似對對象的一個(gè)標(biāo)志,那么這個(gè)標(biāo)志就是存放在Java對象的對象頭。Java對象頭里的Mark Word里默認(rèn)的存放的對象的Hashcode,分代年齡和鎖標(biāo)記位。32為JVM Mark Word默認(rèn)存儲結(jié)構(gòu)為(注:java對象頭以及下面的鎖狀態(tài)變化摘自《java并發(fā)編程的藝術(shù)》一書,該書我認(rèn)為寫的足夠好,就沒在自己組織語言班門弄斧了):

image.png

如圖在Mark Word會默認(rèn)存放hasdcode,年齡值以及鎖標(biāo)志位等信息。

Java SE 1.6中,鎖一共有4種狀態(tài),級別從低到高依次是:無鎖狀態(tài)、偏向鎖狀態(tài)、輕量級鎖狀態(tài)和重量級鎖狀態(tài),這幾個(gè)狀態(tài)會隨著競爭情況逐漸升級。鎖可以升級但不能降級,意味著偏向鎖升級成輕量級鎖后不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率。對象的MarkWord變化為下圖:

image.png

3.2 偏向鎖

HotSpot的作者經(jīng)過研究發(fā)現(xiàn),大多數(shù)情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價(jià)更低而引入了偏向鎖。

偏向鎖的獲取

當(dāng)一個(gè)線程訪問同步塊并獲取鎖時(shí),會在對象頭棧幀中的鎖記錄里存儲鎖偏向的線程ID,以后該線程在進(jìn)入和退出同步塊時(shí)不需要進(jìn)行CAS操作來加鎖和解鎖,只需簡單地測試一下對象頭的Mark Word里是否存儲著指向當(dāng)前線程的偏向鎖。如果測試成功,表示線程已經(jīng)獲得了鎖。如果測試失敗,則需要再測試一下Mark Word中偏向鎖的標(biāo)識是否設(shè)置成1(表示當(dāng)前是偏向鎖):如果沒有設(shè)置,則使用CAS競爭鎖;如果設(shè)置了,則嘗試使用CAS將對象頭的偏向鎖指向當(dāng)前線程

偏向鎖的撤銷

偏向鎖使用了一種等到競爭出現(xiàn)才釋放鎖的機(jī)制,所以當(dāng)其他線程嘗試競爭偏向鎖時(shí),持有偏向鎖的線程才會釋放鎖。

image.png

如圖,偏向鎖的撤銷,需要等待全局安全點(diǎn)(在這個(gè)時(shí)間點(diǎn)上沒有正在執(zhí)行的字節(jié)碼)。它會首先暫停擁有偏向鎖的線程,然后檢查持有偏向鎖的線程是否活著,如果線程不處于活動狀態(tài),則將對象頭設(shè)置成無鎖狀態(tài);如果線程仍然活著,擁有偏向鎖的棧會被執(zhí)行,遍歷偏向?qū)ο蟮逆i記錄,棧中的鎖記錄和對象頭的Mark Word要么重新偏向于其他線程,要么恢復(fù)到無鎖或者標(biāo)記對象不適合作為偏向鎖,最后喚醒暫停的線程。

下圖線程1展示了偏向鎖獲取的過程,線程2展示了偏向鎖撤銷的過程。

image.png

如何關(guān)閉偏向鎖

偏向鎖在Java 6和Java 7里是默認(rèn)啟用的,但是它在應(yīng)用程序啟動幾秒鐘之后才激活,如有必要可以使用JVM參數(shù)來關(guān)閉延遲:-XX:BiasedLockingStartupDelay=0。如果你確定應(yīng)用程序里所有的鎖通常情況下處于競爭狀態(tài),可以通過JVM參數(shù)關(guān)閉偏向鎖:-XX:-UseBiasedLocking=false,那么程序默認(rèn)會進(jìn)入輕量級鎖狀態(tài)

3.3 輕量級鎖

加鎖

線程在執(zhí)行同步塊之前,JVM會先在當(dāng)前線程的棧楨中創(chuàng)建用于存儲鎖記錄的空間,并將對象頭中的Mark Word復(fù)制到鎖記錄中,官方稱為Displaced Mark Word。然后線程嘗試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當(dāng)前線程獲得鎖,如果失敗,表示其他線程競爭鎖,當(dāng)前線程便嘗試使用自旋來獲取鎖

解鎖

輕量級解鎖時(shí),會使用原子的CAS操作將Displaced Mark Word替換回到對象頭,如果成功,則表示沒有競爭發(fā)生。如果失敗,表示當(dāng)前鎖存在競爭,鎖就會膨脹成重量級鎖。下圖是兩個(gè)線程同時(shí)爭奪鎖,導(dǎo)致鎖膨脹的流程圖。

image.png

因?yàn)樽孕龝腃PU,為了避免無用的自旋(比如獲得鎖的線程被阻塞住了),一旦鎖升級成重量級鎖,就不會再恢復(fù)到輕量級鎖狀態(tài)。當(dāng)鎖處于這個(gè)狀態(tài)下,其他線程試圖獲取鎖時(shí),都會被阻塞住,當(dāng)持有鎖的線程釋放鎖之后會喚醒這些線程,被喚醒的線程就會進(jìn)行新一輪的奪鎖之爭。

3.5 各種鎖的比較

image.png

4. 一個(gè)例子

經(jīng)過上面的理解,我們現(xiàn)在應(yīng)該知道了該怎樣解決了。更正后的代碼為:

public class SynchronizedDemo implements Runnable {
    private static int count = 0;

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new SynchronizedDemo());
            thread.start();
        }
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("result: " + count);
    }

    @Override
    public void run() {
        synchronized (SynchronizedDemo.class) {
            for (int i = 0; i < 1000000; I++)
                count++;
        }
    }
}

開啟十個(gè)線程,每個(gè)線程在原值上累加1000000次,最終正確的結(jié)果為10X1000000=10000000,這里能夠計(jì)算出正確的結(jié)果是因?yàn)樵谧隼奂硬僮鲿r(shí)使用了同步代碼塊,這樣就能保證每個(gè)線程所獲得共享變量的值都是當(dāng)前最新的值,如果不使用同步的話,就可能會出現(xiàn)A線程累加后,而B線程做累加操作有可能是使用原來的就值,即“臟值”。這樣,就導(dǎo)致最終的計(jì)算結(jié)果不是正確的。而使用Syncnized就可能保證內(nèi)存可見性,保證每個(gè)線程都是操作的最新值。這里只是一個(gè)示例性的demo,聰明的你,還有其他辦法嗎?

參考文獻(xiàn)

《java并發(fā)編程的藝術(shù)》

作者:你聽___
鏈接:http://www.lxweimin.com/p/d53bf830fa09
來源:簡書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,976評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,958評論 2 373

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

  • 原創(chuàng)文章&經(jīng)驗(yàn)總結(jié)&從校招到A廠一路陽光一路滄桑 詳情請戳www.codercc.com[http://www.c...
    你聽___閱讀 163,579評論 21 254
  • 第2章 java并發(fā)機(jī)制的底層實(shí)現(xiàn)原理 Java中所使用的并發(fā)機(jī)制依賴于JVM的實(shí)現(xiàn)和CPU的指令。 2.1 vo...
    kennethan閱讀 1,447評論 0 2
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,720評論 0 11
  • 那是空中的芭蕾 優(yōu)美 那是冰雪的焰火 熱情 那是青春的飛翔 年輕 那是生命的律動 感人
    牧馬天山閱讀 137評論 0 3
  • 秋的訊息 你最先知道 你是秋的驕子 去年 記得是在你身旁 彎腰撫摸你絲縷悠香 你的千姿百態(tài) 留在微微細(xì)雨中 爭奇斗...
    赤子天涯心閱讀 330評論 0 5