Java多線程

01 |可見性蒋纬、原子性和有序性問題:并發(fā)編程Bug的源頭


原子性:線程切換導(dǎo)致原子性。

可見性:CPU緩存導(dǎo)致可見性来庭。

有序性:編譯優(yōu)化導(dǎo)致有序性宣决。



02 | Java內(nèi)存模型:看Java如何解決可見性和有序性問題


Java 內(nèi)存模型規(guī)范了 JVM 如何提供按需禁用緩存和編譯優(yōu)化的方法。具體來說吆你,這些方法包括 volatile弦叶、synchronized 和 final 三個關(guān)鍵字,以及六項 Happens-Before 規(guī)則妇多。

volatile:告訴編譯器伤哺,對這個變量的讀寫,不能使用 CPU 緩存者祖,必須從內(nèi)存中讀取或者寫入默责。

Happens-Before 規(guī)則

前面一個操作的結(jié)果對后續(xù)操作是可見的。在 Java 語言里面咸包,Happens-Before 的語義本質(zhì)上是一種可見性,A Happens-Before B 意味著 A 事件對 B 事件來說是可見的杖虾,無論 A 事件和 B 事件是否發(fā)生在同一個線程里烂瘫。

1、程序順序性原則?

按照程序順序奇适,前面的操作 Happens-Before 于后續(xù)的任意操作坟比。(此處指統(tǒng)一代碼塊中的單線程思維)

2、volatile原則

對一個 volatile 變量的寫操作嚷往, Happens-Before 于后續(xù)對這個 volatile 變量的讀操作葛账。(結(jié)合規(guī)則3(傳遞性)使用)(寫變量“v=true”? ? 讀變量 “v=true”)

3、傳遞性

如果 A Happens-Before B皮仁,且 B Happens-Before C籍琳,那么 A Happens-Before C。

舉例如下:

代碼:class VolatileExample {

? int x = 0;

? volatile boolean v = false;

? public void writer() {

? ? x = 42;

? ? v = true;

? }

? public void reader() {

? ? if (v == true) {

? ? ? // 這里x會是多少呢贷祈?

? ? }

? }

分析:上面的示例代碼趋急,假設(shè)線程 A 執(zhí)行 writer() 方法,按照 volatile 語義势誊,會把變量 “v=true” 寫入內(nèi)存呜达;假設(shè)線程 B 執(zhí)行 reader() 方法,同樣按照 volatile 語義粟耻,線程 B 會從內(nèi)存中讀取變量 v查近,如果線程 B 看到 “v == true” 時眉踱,那么線程 B 看到的變量 x 是42

PS:利用了volatite和傳遞性原則


4、管程中鎖的規(guī)則

對一個鎖的解鎖 Happens-Before 于后續(xù)對這個鎖的加鎖——(假設(shè) x 的初始值是 10霜威,線程 A 執(zhí)行完代碼塊后 x 的值會變成 12(執(zhí)行完自動釋放鎖)谈喳,線程 B 進(jìn)入代碼塊時,能夠看到線程 A 對 x 的寫操作侥祭,也就是線程 B 能夠看到 x==12)叁执。這個地方對于synchronized(管程中的一種)同樣適用,一個鎖的解鎖?Happens-Before后續(xù)這個鎖的加鎖矮冬。

5谈宛、線程start()原則

它是指主線程 A 啟動子線程 B 后,子線程 B 能夠看到主線程在啟動子線程 B 前的操作

6胎署、線程join原則

主線程 A 等待子線程 B 完成(主線程 A 通過調(diào)用子線程 B 的 join() 方法實現(xiàn))吆录,當(dāng)子線程 B 完成后(主線程 A 中 join() 方法返回),主線程能夠看到子線程的操作琼牧。當(dāng)然所謂的“看到”恢筝,指的是對共享變量的操作。

final

在 1.5 以后 Java 內(nèi)存模型對 final 類型變量的重排進(jìn)行了約束【薹唬現(xiàn)在只要我們提供正確構(gòu)造函數(shù)沒有“逸出”撬槽,就不會出問題了。



03 | 互斥鎖(上):解決原子性問題


我們把一段需要互斥執(zhí)行的代碼稱為臨界區(qū)趾撵。

Java 語言提供的鎖技術(shù):synchronized(鎖和鎖要保護的資源是有對應(yīng)關(guān)系的侄柔,比如你用你家的鎖保護你家的東西,我用我家的鎖保護我家的東西)占调。受保護資源和鎖之間的關(guān)聯(lián)關(guān)系是 N:1 的關(guān)系暂题,但是不能用多把鎖來保護一個資源。

當(dāng)修飾靜態(tài)方法的時候究珊,鎖定的是當(dāng)前類的 Class 對象薪者,在上面的例子中就是 Class X;當(dāng)修飾非靜態(tài)方法的時候剿涮,鎖定的是當(dāng)前實例對象 this言津。

同一線程在調(diào)用自己類中其他 synchronized 方法/塊或調(diào)用父類的 synchronized 方法/塊都不會阻礙該線程的執(zhí)行。就是說同一線程對同一個對象鎖是可重入的取试,而且同一個線程可以獲取同一把鎖多次纺念,也就是可以多次重入



04 | 互斥鎖(下):如何用一把鎖保護多個資源想括?


細(xì)粒度鎖:用不同的鎖對受保護資源進(jìn)行精細(xì)化管理陷谱,是性能優(yōu)化的一個重要手段。(使用細(xì)粒度鎖是有代價的,這個代價就是可能會導(dǎo)致死鎖)

this 這把鎖可以保護自己的資源烟逊,卻保護不了別人的資源渣窜,就像你不能用自家的鎖來保護別人家的資產(chǎn),也不能用自己的票來保護別人的座位一樣宪躯。

我們提到用同一把鎖來保護多個資源——包場:鎖能覆蓋所有受保護資源

“原子性”的本質(zhì):其實不是不可分割乔宿,不可分割只是外在表現(xiàn),其本質(zhì)是多個資源間有一致性的要求访雪,操作的中間狀態(tài)對外不可見详瑞。(解決原子性問題,是要保證中間狀態(tài)對外不可見臣缀。)

PS:不能用可變對象做鎖



05 | 一不小心就死鎖了坝橡,怎么辦?


死鎖的一個比較專業(yè)的定義是:一組互相競爭資源的線程因互相等待精置,導(dǎo)致“永久”阻塞的現(xiàn)象计寇。

以下四個條件都發(fā)生時才會出現(xiàn)死鎖:

1、互斥脂倦,共享資源 X 和 Y 只能被一個線程占用番宁;

2、占有且等待赖阻,線程 T1 已經(jīng)取得共享資源 X蝶押,在等待共享資源 Y 的時候,不釋放共享資源 X火欧;

3棋电、不可搶占,其他線程不能強行搶占線程 T1 占有的資源布隔;

4、循環(huán)等待稼虎,線程 T1 等待線程 T2 占有的資源衅檀,線程 T2 等待線程 T1 占有的資源,就是循環(huán)等待霎俩。

也就是說只要我們破壞其中一個哀军,就可以成功避免死鎖的發(fā)生(互斥這個條件我們沒有辦法破壞,因為我們用鎖為的就是互斥)

1. 破壞占用且等待條件

從理論上講打却,要破壞這個條件杉适,可以一次性申請所有資源。

2. 破壞不可搶占條件

核心是要能夠主動釋放它占有的資源

3. 破壞循環(huán)等待條件

破壞這個條件柳击,需要對資源進(jìn)行排序猿推,然后按序申請資源。(這個實現(xiàn)非常簡單,我們假設(shè)每個賬戶都有不同的屬性 id蹬叭,這個 id 可以作為排序字段藕咏,申請的時候,我們可以按照從小到大的順序來申請秽五。)



06 | 用“等待-通知”機制優(yōu)化循環(huán)等待


等待 - 通知機制:線程首先獲取互斥鎖孽查,當(dāng)線程要求的條件不滿足時,釋放互斥鎖坦喘,進(jìn)入等待狀態(tài)盲再;當(dāng)要求的條件滿足時,通知等待的線程瓣铣,重新獲取互斥鎖答朋。

Java 語言內(nèi)置的 synchronized 配合 wait()、notify()坯沪、notifyAll() 這三個方法就能輕松實現(xiàn)等待 - 通知機制

等待隊列和互斥鎖是一對一的關(guān)系绿映,每個互斥鎖都有自己獨立的等待隊列。

wait() 操作工作原理圖

notify() 操作工作原理圖

注意:被通知的線程要想重新執(zhí)行腐晾,仍然需要獲取到互斥鎖(因為曾經(jīng)獲取的鎖在調(diào)用 wait() 時已經(jīng)釋放了)

等待 - 通知機制中叉弦,我們需要考慮以下四個要素。

互斥鎖:上一篇文章我們提到 Allocator 需要是單例的藻糖,所以我們可以用 this 作為互斥鎖淹冰。

線程要求的條件:轉(zhuǎn)出賬戶和轉(zhuǎn)入賬戶都沒有被分配過。

何時等待:線程要求的條件不滿足就等待巨柒。

何時通知:當(dāng)有線程釋放賬戶時就通知樱拴。



07 | 安全性、活躍性以及性能問題


并發(fā)編程主要要注意三個問題:安全性問題洋满、活躍性問題和性能問題

安全性問題

線程安全:程序按照我們期望的執(zhí)行晶乔,不要讓我們感到意外

數(shù)據(jù)競爭:當(dāng)多個線程同時訪問同一數(shù)據(jù)

競態(tài)(爭)條件:指的是程序的執(zhí)行結(jié)果依賴線程執(zhí)行的順序

活躍性問題

活躍性問題:指的是某個操作無法執(zhí)行下去。

死鎖”就是一種典型的活躍性問題牺勾,當(dāng)然除了死鎖外正罢,還有兩種情況,分別是“活鎖”和“饑餓”驻民。

有時線程雖然沒有發(fā)生阻塞翻具,但仍然會存在執(zhí)行不下去的情況,這就是所謂的“活鎖”

“饑餓”指的是線程因無法訪問所需資源而無法執(zhí)行下去的情況——在 CPU 繁忙的情況下回还,優(yōu)先級低的線程得到執(zhí)行的機會很小裆泳,就可能發(fā)生線程“饑餓”;持有鎖的線程柠硕,如果執(zhí)行的時間過長工禾,也可能導(dǎo)致“饑餓”問題。——解決“饑餓”問題的方案帜篇,有三種方案:一是保證資源充足糙捺,二是公平地分配資源,三就是避免持有鎖的線程長時間執(zhí)行笙隙。這三個方案中洪灯,方案一和方案三的適用場景比較有限,因為很多場景下竟痰,資源的稀缺性是沒辦法解決的签钩,持有鎖的線程執(zhí)行的時間也很難縮短。倒是方案二的適用場景相對來說更多一些坏快。那如何公平地分配資源呢铅檩?在并發(fā)編程里,主要是使用公平鎖

性能問題

Java SDK 并發(fā)包里之所以有那么多東西莽鸿,有很大一部分原因就是要提升在某個特定領(lǐng)域的性能昧旨。

第一,既然使用鎖會帶來性能問題祥得,那最好的方案自然就是使用無鎖的算法和數(shù)據(jù)結(jié)構(gòu)了兔沃。

第二,減少鎖持有的時間级及。

性能方面的度量指標(biāo)有很多乒疏,我覺得有三個指標(biāo)非常重要,就是:吞吐量饮焦、延遲和并發(fā)量



08 | 管程:并發(fā)編程的萬能鑰匙


管程(Monitor)和信號量是等價的怕吴,所謂等價指的是用管程能夠?qū)崿F(xiàn)信號量,也能用信號量實現(xiàn)管程县踢。

管程转绷,指的是管理共享變量以及對共享變量的操作過程,讓他們支持并發(fā)硼啤∫榫——翻譯為 Java 領(lǐng)域的語言,就是管理類的成員變量和成員方法丙曙,讓這個類是線程安全的爸业。

MESA 模型

在并發(fā)編程領(lǐng)域其骄,有兩大核心問題:一個是互斥亏镰,即同一時刻只允許一個線程訪問共享資源;另一個是同步拯爽,即線程之間如何通信索抓、協(xié)作。

管程解決互斥問題的思路很簡單,就是將共享變量及其對共享變量的操作統(tǒng)一封裝起來逼肯。

封裝變量&封裝對變量的操作

借鑒就醫(yī)流程耸黑,看管程如何解決線程間的同步問題

MEAS管程模型

每個條件變量都對應(yīng)有一個等待隊列

兩個重要的類:ReentrantLock &? Condition

final Lock lock = new ReentrantLock();

// 條件變量:隊列不滿 final Condition notFull = lock.newCondition();

// 條件變量:隊列不空 final Condition notEmpty = lock.newCondition();



09 | Java線程(上):Java線程的生命周期


通用的線程生命周期基本上可以用下圖這個“五態(tài)模型”來描述。這五態(tài)分別是:初始狀態(tài)篮幢、可運行狀態(tài)大刊、運行狀態(tài)、休眠狀態(tài)和終止?fàn)顟B(tài)三椿。

通用線程狀態(tài)轉(zhuǎn)換圖——五態(tài)模型

Java 語言中線程共有六種狀態(tài)缺菌,分別是:

NEW(初始化狀態(tài))

RUNNABLE(可運行 / 運行狀態(tài))

BLOCKED(阻塞狀態(tài))

WAITING(無時限等待)

TIMED_WAITING(有時限等待)

TERMINATED(終止?fàn)顟B(tài))

Java 線程中的 BLOCKED、WAITING搜锰、TIMED_WAITING 是一種狀態(tài)伴郁,即前面我們提到的休眠狀態(tài)。也就是說只要 Java 線程處于這三種狀態(tài)之一蛋叼,那么這個線程就永遠(yuǎn)沒有 CPU 的使用權(quán)焊傅。

Java 中的線程狀態(tài)轉(zhuǎn)換圖

1. RUNNABLE 與 BLOCKED 的狀態(tài)轉(zhuǎn)換

只有一種場景會觸發(fā)這種轉(zhuǎn)換,就是線程等待 synchronized 的隱式鎖(等待的線程就會從 RUNNABLE 轉(zhuǎn)換到 BLOCKED 狀態(tài)狈涮。而當(dāng)?shù)却木€程獲得 synchronized 隱式鎖時狐胎,就又會從 BLOCKED 轉(zhuǎn)換到 RUNNABLE 狀態(tài)。)

2. RUNNABLE 與 WAITING 的狀態(tài)轉(zhuǎn)換

Object.wait() 薯嗤、 Thread.join() 顽爹、 LockSupport.park()

3. RUNNABLE 與 TIMED_WAITING 的狀態(tài)轉(zhuǎn)換

調(diào)用帶超時參數(shù)的 Thread.sleep(long millis) 方法;獲得 synchronized 隱式鎖的線程骆姐,調(diào)用帶超時參數(shù)的 Object.wait(long timeout) 方法镜粤;調(diào)用帶超時參數(shù)的 Thread.join(long millis) 方法;調(diào)用帶超時參數(shù)的 LockSupport.parkNanos(Object blocker, long deadline) 方法玻褪;調(diào)用帶超時參數(shù)的 LockSupport.parkUntil(long deadline) 方法肉渴。

TIMED_WAITING 和 WAITING 狀態(tài)的區(qū)別,僅僅是觸發(fā)條件多了超時參數(shù)带射。

4. 從 NEW 到 RUNNABLE 狀態(tài)

從 NEW 狀態(tài)轉(zhuǎn)換到 RUNNABLE 狀態(tài)很簡單同规,調(diào)用線程對象的 start()

5. 從 RUNNABLE 到 TERMINATED 狀態(tài)

1、線程執(zhí)行完 run() 方法后窟社,會自動轉(zhuǎn)換到 TERMINATED 狀態(tài)券勺,當(dāng)然如果執(zhí)行 run() 方法的時候異常拋出,也會導(dǎo)致線程終止灿里。? 2关炼、調(diào)用 interrupt() 方法

6、NEW狀態(tài)

新建一個線程對象



010 | Java線程(中):創(chuàng)建多少線程才是合適的匣吊?


要解決這個問題儒拂,首先要分析以下兩個問題:

為什么要使用多線程寸潦?? 多線程的應(yīng)用場景有哪些?

計算機主要有哪些硬件呢社痛?主要是兩類:一個是 I/O见转,一個是 CPU。簡言之蒜哀,在并發(fā)編程領(lǐng)域斩箫,提升性能本質(zhì)上就是提升硬件的利用率,再具體點來說撵儿,就是提升 I/O 的利用率和 CPU 的利用率校焦。(PS: CPUworking跟I/Oworking都是有線程在參與)

我們的程序一般都是 CPU 計算和 I/O 操作交叉執(zhí)行的,所以有CPU密集型跟IO密集型的區(qū)別

CPU 密集型計算

對于 CPU 密集型的計算場景统倒,理論上“線程的數(shù)量 =CPU 核數(shù)”就是最合適的寨典。不過在工程上,線程的數(shù)量一般會設(shè)置為“CPU 核數(shù) +1”房匆,這樣的話耸成,當(dāng)線程因為偶爾的內(nèi)存頁失效或其他原因?qū)е伦枞麜r,這個額外的線程可以頂上浴鸿,從而保證 CPU 的利用率井氢。

I/O 密集型的計算場景

最佳線程數(shù) =1 +(I/O 耗時 / CPU 耗時)? ? 單核CPU

最佳線程數(shù) =CPU 核數(shù) * [ 1 +(I/O 耗時 / CPU 耗時)? ? 多核CPU

目標(biāo)就是讓CPU 和 I/O 設(shè)備的利用率都達(dá)到最高位。將硬件的性能發(fā)揮到極致岳链。

單線程執(zhí)行示意圖

二線程執(zhí)行示意圖

三線程執(zhí)行示意圖



011 | Java線程(下):為什么局部變量是線程安全的花竞?

局部變量的作用域是方法內(nèi)部,也就是說當(dāng)方法執(zhí)行完掸哑,局部變量就沒用了约急,局部變量應(yīng)該和方法同生共死。(局部變量是和方法同生共死的苗分,一個變量如果想跨越方法的邊界厌蔽,就必須創(chuàng)建在堆里。)

兩個線程可以同時用不同的參數(shù)調(diào)用相同的方法摔癣,那調(diào)用棧和線程之間是什么關(guān)系呢奴饮?答案是:每個線程都有自己獨立的調(diào)用棧。

線程與調(diào)用棧的關(guān)系圖

沒有共享择浊,就沒有傷害戴卜。

附:

當(dāng)調(diào)用方法時,會創(chuàng)建新的棧幀琢岩,并壓入調(diào)用棧投剥;

調(diào)用棧結(jié)構(gòu)

局部變量就是放到了調(diào)用棧里

保護局部變量的調(diào)用棧結(jié)構(gòu)




012 |? 如何用面向?qū)ο笏枷雽懞貌l(fā)程序?


在 Java 語言里粘捎,面向?qū)ο笏枷肽軌蜃尣l(fā)編程變得更簡單薇缅。

用面向?qū)ο笏枷雽懞貌l(fā)程序:

1、封裝共享變量? 2攒磨、識別共享變量間的約束條件? 3泳桦、制定并發(fā)訪問策略

1、封裝共享變量

面向?qū)ο笏枷肜锩嬗幸粋€很重要的特性是封裝娩缰,封裝的通俗解釋就是將屬性和實現(xiàn)細(xì)節(jié)封裝在對象內(nèi)部灸撰,外界對象只能通過目標(biāo)對象提供的公共方法來間接訪問這些內(nèi)部屬性.將共享變量作為對象屬性封裝在內(nèi)部,對所有公共方法制定并發(fā)訪問策略拼坎。

對于不會發(fā)生變化的共享變量浮毯,建議你用 final 關(guān)鍵字來修飾。

2泰鸡、識別共享變量間的約束條件

這些約束條件债蓝,決定了并發(fā)訪問策略。識別出所有共享變量之間的約束條件盛龄,如果約束條件識別不足饰迹,很可能導(dǎo)致制定的并發(fā)訪問策略南轅北轍。

共享變量之間的約束條件余舶,反映在代碼里啊鸭,基本上都會有 if 語句,所以匿值,一定要特別注意競態(tài)條件赠制。

3、制定并發(fā)訪問策略

避免共享:避免共享的技術(shù)主要是利于線程本地存儲以及為每個任務(wù)分配獨立的線程挟憔。

不變模式:這個在 Java 領(lǐng)域應(yīng)用的很少钟些,但在其他領(lǐng)域卻有著廣泛的應(yīng)用,例如 Actor 模式绊谭、CSP 模式以及函數(shù)式編程的基礎(chǔ)都是不變模式厘唾。

管程及其他同步工具:Java 領(lǐng)域萬能的解決方案是管程,但是對于很多特定場景龙誊,使用 Java 并發(fā)包提供的讀寫鎖抚垃、并發(fā)容器等同步工具會更好。

優(yōu)先使用成熟的工具類:Java SDK 并發(fā)包里提供了豐富的工具類趟大,基本上能滿足你日常的需要鹤树,建議你熟悉它們,用好它們逊朽,而不是自己再“發(fā)明輪子”罕伯,畢竟并發(fā)工具類不是隨隨便便就能發(fā)明成功的。

迫不得已時才使用低級的同步原語:低級的同步原語主要指的是 synchronized叽讳、Lock追他、Semaphore 等坟募,這些雖然感覺簡單,但實際上并沒那么簡單邑狸,一定要小心使用懈糯。

避免過早優(yōu)化:安全第一,并發(fā)程序首先要保證安全单雾,出現(xiàn)性能瓶頸后再優(yōu)化赚哗。在設(shè)計期和開發(fā)期,很多人經(jīng)常會情不自禁地預(yù)估性能的瓶頸硅堆,并對此實施優(yōu)化屿储,但殘酷的現(xiàn)實卻是:性能瓶頸不是你想預(yù)估就能預(yù)估的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末渐逃,一起剝皮案震驚了整個濱河市够掠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌茄菊,老刑警劉巖祖屏,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異买羞,居然都是意外死亡袁勺,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門畜普,熙熙樓的掌柜王于貴愁眉苦臉地迎上來期丰,“玉大人,你說我怎么就攤上這事吃挑《鄣矗” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵舶衬,是天一觀的道長埠通。 經(jīng)常有香客問我,道長逛犹,這世上最難降的妖魔是什么端辱? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任璧针,我火速辦了婚禮髓帽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘栋操。我一直安慰自己码撰,他們只是感情好渗柿,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著脖岛,像睡著了一般朵栖。 火紅的嫁衣襯著肌膚如雪颊亮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天陨溅,我揣著相機與錄音终惑,去河邊找鬼。 笑死声登,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的揣苏。 我是一名探鬼主播悯嗓,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼卸察!你這毒婦竟也來了脯厨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤坑质,失蹤者是張志新(化名)和其女友劉穎合武,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涡扼,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡稼跳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吃沪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片汤善。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖票彪,靈堂內(nèi)的尸體忽然破棺而出红淡,到底是詐尸還是另有隱情,我是刑警寧澤降铸,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布在旱,位于F島的核電站,受9級特大地震影響推掸,放射性物質(zhì)發(fā)生泄漏桶蝎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一谅畅、第九天 我趴在偏房一處隱蔽的房頂上張望俊嗽。 院中可真熱鬧,春花似錦铃彰、人聲如沸绍豁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽竹揍。三九已至敬飒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間芬位,已是汗流浹背无拗。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留昧碉,地道東北人英染。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像被饿,于是被迫代替她去往敵國和親四康。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

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