Java 內(nèi)存模型詳解

概述

Java的內(nèi)存模型(Java Memory Model )簡稱JMM。首先應該明白,Java內(nèi)存模型是一個規(guī)范殉疼,主要規(guī)定了以下兩點:

  1. 規(guī)定了一個線程如何以及何時可以看到其他線程修改過后的共享變量的值欢搜,即線程之間共享變量的可見性。
  2. 如何在需要的時候對共享變量進行同步抖剿。

JMM定義了Java虛擬機(JVM)在計算機內(nèi)存(RAM)中的工作方式朽寞。

而在并發(fā)編程中,我們所要處理的兩個關鍵問題就是這兩條標準的體現(xiàn):線程之間如何通信以及線程之間如何同步斩郎。通信是指線程之間以何種機制來交換信息脑融。在命令式的編程中,線程之間的通信機制有兩種:共享內(nèi)存和消息傳遞缩宜。

在共享內(nèi)存并發(fā)的模型里肘迎,線程之間共享程序的公共狀態(tài),線程之間通過讀-寫內(nèi)存中的公共狀態(tài)來隱式進行通信。典型的共享內(nèi)存通信方式就是通過共享對象進行通信妓布。

在消息傳遞的并發(fā)模型里窿侈,線程之間沒有公共狀態(tài),線程之間必須通過明確的發(fā)送消息來顯示進行通信秋茫,在java中典型的消息傳遞方式就是wait()notify()史简。

同步是指程序用于控制不同線程之間操作發(fā)生相對順序的機制。

在共享內(nèi)存并發(fā)模型里肛著,同步是顯示進行的圆兵,程序員必須顯示指定某個方法或某段代碼需要在線程之間互斥進行。

在消息傳遞的并發(fā)模型里枢贿,由于消息的發(fā)送必須在消息的接受之前殉农,因此同步是隱式進行的。

Java的并發(fā)采用的就是共享內(nèi)存模型局荚,Java線程之間的通信總是隱式進行的超凳,整個通信過程對程序員是完全透明的。

上面講到了Java線程之間的通信采用的是過共享內(nèi)存模型耀态,這里提到的共享內(nèi)存模型指的就是Java內(nèi)存模型(簡稱JMM)轮傍,JMM決定一個線程對共享變量的寫入何時對另一個線程可見。從抽象的角度來看首装,JMM定義了線程和主內(nèi)存之間的抽象關系:線程之間的共享變量存儲在主內(nèi)存(main memory)中创夜,每個線程都有一個私有的本地內(nèi)存(local memory),本地內(nèi)存中存儲了該線程以讀/寫共享變量的副本仙逻。本地內(nèi)存是JMM的一個抽象概念驰吓,并不真實存在。它涵蓋了緩存系奉,寫緩沖區(qū)檬贰,寄存器以及其他的硬件和編譯器優(yōu)化。

從上圖來看缺亮,線程A與線程B之間如要通信的話翁涤,必須要經(jīng)歷下面2個步驟:

  1. 首先,線程A把本地內(nèi)存A中更新過的共享變量刷新到主內(nèi)存中去瞬内。
  2. 然后迷雪,線程B到主內(nèi)存中去讀取線程A之前已更新過的共享變量限书。

下面通過示意圖來說明這兩個步驟:

如上圖所示虫蝶,本地內(nèi)存A和B有主內(nèi)存中共享變量x的副本。假設初始時倦西,這三個內(nèi)存中的x值都為0能真。線程A在執(zhí)行時,把更新后的x值(假設值為1)臨時存放在自己的本地內(nèi)存A中。當線程A和線程B需要通信時粉铐,線程A首先會把自己本地內(nèi)存中修改后的x值刷新到主內(nèi)存中疼约,此時主內(nèi)存中的x值變?yōu)榱?。隨后蝙泼,線程B到主內(nèi)存中去讀取線程A更新后的x值程剥,此時線程B的本地內(nèi)存的x值也變?yōu)榱?。

從整體來看汤踏,這兩個步驟實質上是線程A在向線程B發(fā)送消息织鲸,而且這個通信過程必須要經(jīng)過主內(nèi)存。JMM通過控制主內(nèi)存與每個線程的本地內(nèi)存之間的交互溪胶,來為java程序員提供內(nèi)存可見性保證搂擦。

上面也說到了,Java內(nèi)存模型只是一個抽象概念哗脖,那么它在Java中具體是怎么工作的呢瀑踢?為了更好的理解Java內(nèi)存模型的工作方式,下面就JVM對Java內(nèi)存模型的實現(xiàn)才避、硬件內(nèi)存模型及它們之間的橋接做詳細介紹橱夭。

JVM對Java內(nèi)存模型的實現(xiàn)

在JVM內(nèi)部,Java虛擬機在執(zhí)行Java程序的過程中會把它所管理的內(nèi)存劃分為若干不同的數(shù)據(jù)區(qū)域桑逝,這些區(qū)域都有各自的用途以及創(chuàng)建和銷毀的時間徘钥。

主要區(qū)域如下圖所示:堆(Heap),虛擬機棧(VM Stack)肢娘,方法區(qū)(Method Area)呈础,本地方法棧(Native Method Stack),程序計數(shù)器(PC Register)橱健。

? 從上面的圖中可以看出:

    1. 堆和方法區(qū)線程共享的而钞;
    1. 棧和程序計數(shù)器是線程私有的。

詳細說明和異常拋出:

虛擬機棧: 每一個運行在Java虛擬機上的線程都擁有自己的線程棧拘荡,虛擬機棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法在執(zhí)行的時候都會創(chuàng)建一個棧幀用于存儲局部變量表臼节、操作數(shù)棧、動態(tài)鏈表珊皿、方法出口信息等网缝。每一個方法從調用直至執(zhí)行完成的過程,就對應著一個棧幀在虛擬機棧中入棧到出棧的過程蟋定。棧的生命周期與線程相同粉臊。

? 線程棧之間是相互隔離的,一個線程僅能訪問自己的線程棧驶兜。一個線程創(chuàng)建的本地變量對其它線程不可見扼仲,僅自己可見远寸。即使兩個線程執(zhí)行同樣的代碼,這兩個線程任然在在自己的線程棧中的代碼來創(chuàng)建本地變量屠凶。因此驰后,每個線程擁有每個本地變量的獨有版本。

? 所有原始類型的本地變量都存放在線程棧上矗愧,因此對其它線程不可見灶芝。一個線程可能向另一個線程傳遞一個原始類型變量的拷貝,但是它不能共享這個原始類型變量自身唉韭。

本地方法棧:本地方法棧與虛擬機棧的作用相似监署,不同之處在于虛擬機棧為虛擬機執(zhí)行的Java方法服務,而本地方法棧則為虛擬機使用到的Native方法服務纽哥。有的虛擬機直接把本地方法棧和虛擬機棧合二為一钠乏。

程序計數(shù)器:程序計數(shù)器保存著每一條線程下一次執(zhí)行指令位置。

堆:用來保存程序中所創(chuàng)建的所有對象春塌、數(shù)組元素晓避。堆內(nèi)存在線程之間是共享的。

方法區(qū):方法區(qū)是各個線程共享的內(nèi)存區(qū)域只壳,它用于存儲已被虛擬機加載的類信息俏拱、常量、靜態(tài)變量吼句、即時編譯器編譯后的代碼等數(shù)據(jù) 锅必。

數(shù)據(jù)存儲總結:

一個本地變量如果是原始類型,那么它會被完全存儲到棧區(qū)惕艳。
一個本地變量也有可能是一個對象的引用搞隐,這種情況下,這個本地引用會被存儲到棧中远搪,但是對象本身仍然存儲在堆區(qū)劣纲。

對于一個對象的成員方法,這些方法中包含本地變量谁鳍,仍需要存儲在棧區(qū)癞季,即使它們所屬的對象在堆區(qū)。
對于一個對象的成員變量倘潜,不管它是原始類型還是包裝類型绷柒,都會被存儲到堆區(qū)。

Static類型的變量以及類本身相關信息都會隨著類本身存儲在堆區(qū)涮因。

堆中的對象可以被多線程共享废睦。如果一個線程獲得一個對象的引用,它便可訪問這個對象的成員變量蕊退。如果兩個線程同時調用了同一個對象的同一個方法郊楣,那么這兩個線程便可同時訪問這個對象的成員變量,但是對于該對象的本地變量瓤荔,每個線程都會拷貝一份到自己的線程棧中净蚤。也就是,如果兩個線程同時訪問同一個對象的私有變量输硝,這時他們獲得的是這個對象的私有拷貝今瀑。

下圖展示了上面描述的過程:

線程并發(fā)的三大概念:原子性,有序性点把,可見性

? 1.定義

原子性:即一個操作或者多個操作 要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷橘荠,要么就都不執(zhí)行。

? 2.實例

一個很經(jīng)典的例子就是銀行賬戶轉賬問題:

比如從賬戶A向賬戶B轉1000元郎逃,那么必然包括2個操作:從賬戶A減去1000元哥童,往賬戶B加上1000元。

試想一下褒翰,如果這2個操作不具備原子性贮懈,會造成什么樣的后果。假如從賬戶A減去1000元之后优训,操作突然中止朵你。這樣就會導致賬戶A雖然減去了1000元,但是賬戶B沒有收到這個轉過來的1000元揣非。

所以這2個操作必須要具備原子性才能保證不出現(xiàn)一些意外的問題抡医。

同樣地反映到并發(fā)編程中會出現(xiàn)什么結果呢?

舉個最簡單的例子早敬,大家想一下假如為一個32位的變量賦值過程不具備原子性的話忌傻,會發(fā)生什么后果?

i = 9;

假若一個線程執(zhí)行到這個語句時搞监,我暫且假設為一個32位的變量賦值包括兩個過程:為低16位賦值芯勘,為高16位賦值。

那么就可能發(fā)生一種情況:當將低16位數(shù)值寫入之后腺逛,突然被中斷荷愕,而此時又有一個線程去讀取i的值,那么讀取到的就是錯誤的數(shù)據(jù)棍矛。

? 3.Java中的原子性

在Java中安疗,對基本數(shù)據(jù)類型的變量的讀取和賦值操作是原子性操作,即這些操作是不可被中斷的够委,要么執(zhí)行荐类,要么不執(zhí)行。

上面一句話雖然看起來簡單茁帽,但是理解起來并不是那么容易玉罐∏停看下面一個例子i:

請分析以下哪些操作是原子性操作:

x = 10; //語句1
y = x; //語句2
x++; //語句3
x = x + 1; //語句4

咋一看,可能會說上面的4個語句中的操作都是原子性操作吊输。其實只有語句1是原子性操作饶号,其他三個語句都不是原子性操作。

語句1是直接將數(shù)值10賦值給x季蚂,也就是說線程執(zhí)行這個語句的會直接將數(shù)值10寫入到工作內(nèi)存中茫船。

語句2實際上包含2個操作,它先要去讀取x的值扭屁,再將x的值寫入工作內(nèi)存算谈,雖然讀取x的值以及 將x的值寫入工作內(nèi)存 這兩個操作都是原子性操作,但是合起來就不是原子性操作了料滥。

同樣的然眼,x++和 x = x+1包括3個操作:讀取x的值,進行加1操作葵腹,寫入新的值罪治。

所以上面4個語句只有語句1的操作具備原子性。

也就是說礁蔗,只有簡單的讀取觉义、賦值(而且必須是將數(shù)字賦值給某個變量,變量之間的相互賦值不是原子操作)才是原子操作浴井。

從上面可以看出晒骇,Java內(nèi)存模型只保證了基本讀取和賦值是原子性操作,如果要實現(xiàn)更大范圍操作的原子性磺浙,可以通過synchronized和Lock來實現(xiàn)洪囤。由于synchronized和Lock能夠保證任一時刻只有一個線程執(zhí)行該代碼塊,那么自然就不存在原子性問題了撕氧,從而保證了原子性瘤缩。

關于synchronized和Lock的使用,參考:關于synchronized和ReentrantLock之多線程同步詳解

? 1.定義:在執(zhí)行程序時伦泥,為了提高性能剥啤,編譯器和處理器會對指令做重排序。

下面解釋一下什么是指令重排序不脯,一般來說府怯,處理器為了提高程序運行效率,可能會對輸入代碼進行優(yōu)化防楷,它不保證程序中各個語句的執(zhí)行先后順序同代碼中的順序一致牺丙,但是它會保證程序最終執(zhí)行結果和代碼順序執(zhí)行的結果是一致的。

? 2.實例

int i = 0;
boolean flag = false;
i = 1; //語句1
flag = true; //語句2

上面代碼定義了一個int型變量复局,定義了一個boolean類型變量冲簿,然后分別對兩個變量進行賦值操作粟判。從代碼順序上看,語句1是在語句2前面的峦剔,那么JVM在真正執(zhí)行這段代碼的時候會保證語句1一定會在語句2前面執(zhí)行嗎档礁?不一定,為什么呢羊异?這里可能會發(fā)生指令重排序(Instruction Reorder)事秀。

比如上面的代碼中彤断,語句1和語句2誰先執(zhí)行對最終的程序結果并沒有影響野舶,那么就有可能在執(zhí)行過程中,語句2先執(zhí)行而語句1后執(zhí)行宰衙。

但是要注意平道,雖然處理器會對指令進行重排序,但是它會保證程序最終結果會和代碼順序執(zhí)行結果相同供炼,那么它靠什么保證的呢一屋?再看下面一個例子:

int a = 10; //語句1
int r = 2; //語句2
a = a + 3; //語句3
r = a*a; //語句4

這段代碼有4個語句,那么可能的一個執(zhí)行順序是:

那么可不可能是這個執(zhí)行順序呢: 語句2 語句1 語句4 語句3

不可能袋哼,因為處理器在進行重排序時是會考慮指令之間的數(shù)據(jù)依賴性冀墨,如果一個指令Instruction 2必須用到Instruction 1的結果,那么處理器會保證Instruction 1會在Instruction 2之前執(zhí)行涛贯。

雖然重排序不會影響單個線程內(nèi)程序執(zhí)行的結果诽嘉,但是多線程呢?下面看一個例子:

//線程1:
context = loadContext();   //語句1
inited = true;             //語句2
 
 //線程2:
while(!inited ){
   sleep()
}
doSomethingwithconfig(context);

上面代碼中弟翘,由于語句1和語句2沒有數(shù)據(jù)依賴性虫腋,因此可能會被重排序。假如發(fā)生了重排序稀余,在線程1執(zhí)行過程中先執(zhí)行語句2悦冀,而此是線程2會以為初始化工作已經(jīng)完成,那么就會跳出while循環(huán)睛琳,去執(zhí)行doSomethingwithconfig(context)方法盒蟆,而此時context并沒有被初始化,就會導致程序出錯师骗。

從上面可以看出茁影,指令重排序不會影響單個線程的執(zhí)行,但是會影響到線程并發(fā)執(zhí)行的正確性丧凤。

也就是說募闲,要想并發(fā)程序正確地執(zhí)行,必須要保證原子性愿待、可見性以及有序性浩螺。只要有一個沒有被保證靴患,就有可能會導致程序運行不正確。

3.Java中的有序性

在Java內(nèi)存模型中要出,允許編譯器和處理器對指令進行重排序鸳君,但是重排序過程不會影響到單線程程序的執(zhí)行,卻會影響到多線程并發(fā)執(zhí)行的正確性患蹂。

在Java里面或颊,可以通過volatile關鍵字來保證一定的“有序性”。另外可以通過synchronized和Lock來保證有序性传于,很顯然囱挑,synchronized和Lock保證每個時刻是有一個線程執(zhí)行同步代碼,相當于是讓線程順序執(zhí)行同步代碼沼溜,自然就保證了有序性平挑。

關于volatile 和 sychronized的區(qū)別詳見:volatile和synchronized的區(qū)別

? 1.定義:可見性是指當多個線程訪問同一個變量時,一個線程修改了這個變量的值系草,其他線程能夠立即看得到修改的值通熄。

? 2.實例:

//線程1執(zhí)行的代碼
int i = 0;
i = 10;
 
//線程2執(zhí)行的代碼
j = i;

? 由上面的分析可知,當線程1執(zhí)行 i =10這句時找都,會先把i的初始值加載到自己線程的工作內(nèi)存中唇辨,然后賦值為10,那么在線程1的工作內(nèi)存當中i的值變?yōu)?0了能耻,卻沒有立即寫入到主存當中赏枚。

此時線程2執(zhí)行 j = i,它會先去主存讀取i的值并加載到線程2的工作內(nèi)存當中嚎京,注意此時內(nèi)存當中i的值還是0嗡贺,那么就會使得j的值為0,而不是10.

這就是可見性問題鞍帝,線程1對變量i修改了之后诫睬,線程2沒有立即看到線程1修改的值。

3.Java中的可見性

對于可見性帕涌,Java提供了volatile關鍵字來保證可見性摄凡。

當一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存蚓曼,當有其他線程需要讀取時亲澡,它會去內(nèi)存中讀取新值。

而普通的共享變量不能保證可見性纫版,因為普通共享變量被修改之后床绪,什么時候被寫入主存是不確定的,當其他線程去讀取時,此時內(nèi)存中可能還是原來的舊值癞己,因此無法保證可見性膀斋。

另外,通過synchronized和Lock也能夠保證可見性痹雅,synchronized和Lock能保證同一時刻只有一個線程獲取鎖然后執(zhí)行同步代碼仰担,并且在釋放鎖之前會將對變量的修改刷新到主存當中。因此可以保證可見性绩社。

上面講到了摔蓝,通過內(nèi)存屏障可以禁止特定類型處理器的重排序,從而讓程序按我們預想的流程去執(zhí)行愉耙。內(nèi)存屏障贮尉,又稱內(nèi)存柵欄,是一個CPU指令劲阎,基本上它是一條這樣的指令:

  1. 保證特定操作的執(zhí)行順序绘盟。
  2. 影響某些數(shù)據(jù)(或則是某條指令的執(zhí)行結果)的內(nèi)存可見性鸠真。

編譯器和CPU能夠重排序指令悯仙,保證最終相同的結果,嘗試優(yōu)化性能吠卷。插入一條Memory Barrier會告訴編譯器和CPU:不管什么指令都不能和這條Memory Barrier指令重排序锡垄。

Memory Barrier所做的另外一件事是強制刷出各種CPU cache,如一個Write-Barrier(寫入屏障)將刷出所有在Barrier之前寫入 cache 的數(shù)據(jù)祭隔,因此货岭,任何CPU上的線程都能讀取到這些數(shù)據(jù)的最新版本。

這和java有什么關系疾渴?上面java線程并發(fā)中講到的volatile就是基于Memory Barrier實現(xiàn)的千贯。

如果一個變量是volatile修飾的,JMM會在寫入這個字段之后插進一個Write-Barrier指令搞坝,并在讀這個字段之前插入一個Read-Barrier指令搔谴。這意味著姊扔,如果寫入一個volatile變量绑榴,就可以保證:

  1. 一個線程寫入變量a后忘嫉,任何線程訪問該變量都會拿到最新值映挂。
  2. 在寫入變量a之前的寫入操作绵载,其更新的數(shù)據(jù)對于其他線程也是可見的宦言。因為Memory Barrier會刷出cache中的所有先前的寫入蔽氨。

從jdk5開始玉雾,java使用新的JSR-133內(nèi)存模型融师,基于happens-before的概念來闡述操作之間的內(nèi)存可見性右钾。

在JMM中,如果一個操作的執(zhí)行結果需要對另一個操作可見,那么這兩個操作之間必須要存在happens-before關系舀射,這里的兩個操作既可以在同一個線程灭将,也可以在不同的兩個線程中。

與程序員密切相關的happens-before規(guī)則如下:

  1. 程序順序規(guī)則:一個線程內(nèi)后控,按照代碼順序庙曙,書寫在前面的操作先行發(fā)生于書寫在后面的操作
  2. 監(jiān)視器鎖規(guī)則:一個unLock操作先行發(fā)生于后面對同一個鎖的lock操作。
  3. volatile域規(guī)則:對一個變量的寫操作先行發(fā)生于后面對這個變量的讀操作
  4. 傳遞性規(guī)則:如果操作A先行發(fā)生于操作B浩淘,而操作B又先行發(fā)生于操作C捌朴,則可以得出操作A先行發(fā)生于操作C。

下面我們來解釋一下前4條規(guī)則:

第一條對于程序次序規(guī)則來說张抄,就是一段程序代碼的執(zhí)行在單個線程中看起來是有序的砂蔽。注意,雖然這條規(guī)則中提到“書寫在前面的操作先行發(fā)生于書寫在后面的操作”署惯,這個應該是程序看起來執(zhí)行的順序是按照代碼順序執(zhí)行的左驾,但是虛擬機可能會對程序代碼進行指令重排序。雖然進行重排序极谊,但是最終執(zhí)行的結果是與程序順序執(zhí)行的結果一致的诡右,它只會對不存在數(shù)據(jù)依賴性的指令進行重排序。因此轻猖,在單個線程中帆吻,程序執(zhí)行看起來是有序執(zhí)行的,這一點要注意理解咙边。事實上猜煮,這個規(guī)則是用來保證程序在單線程中執(zhí)行結果的正確性,但無法保證程序在多線程中執(zhí)行的正確性败许。

第二條規(guī)則也比較容易理解王带,也就是說無論在單線程中還是多線程中,同一個鎖如果處于被鎖定的狀態(tài)市殷,那么必須先對鎖進行了釋放操作愕撰,后面才能繼續(xù)進行l(wèi)ock操作。

第三條規(guī)則是一條比較重要的規(guī)則被丧。直觀地解釋就是盟戏,如果一個線程先去寫一個變量,然后一個線程去進行讀取甥桂,那么寫入操作肯定會先行發(fā)生于讀操作柿究。

第四條規(guī)則實際上就是體現(xiàn)happens-before原則具備傳遞性。

注意:兩個操作之間具有happens-before關系黄选,并不意味前一個操作必須要在后一個操作之前執(zhí)行蝇摸!僅僅要求前一個操作的執(zhí)行結果婶肩,對于后一個操作是可見的,且前一個操作按順序排在后一個操作之前貌夕。

原文地址:https://my.oschina.net/Declan/blog/1794053

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末律歼,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子啡专,更是在濱河造成了極大的恐慌险毁,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件们童,死亡現(xiàn)場離奇詭異畔况,居然都是意外死亡,警方通過查閱死者的電腦和手機慧库,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門跷跪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人齐板,你說我怎么就攤上這事吵瞻。” “怎么了甘磨?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵橡羞,是天一觀的道長。 經(jīng)常有香客問我宽档,道長尉姨,這世上最難降的妖魔是什么庵朝? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任吗冤,我火速辦了婚禮,結果婚禮上九府,老公的妹妹穿的比我還像新娘椎瘟。我一直安慰自己,他們只是感情好侄旬,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布肺蔚。 她就那樣靜靜地躺著,像睡著了一般儡羔。 火紅的嫁衣襯著肌膚如雪宣羊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天汰蜘,我揣著相機與錄音仇冯,去河邊找鬼。 笑死族操,一個胖子當著我的面吹牛苛坚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼泼舱,長吁一口氣:“原來是場噩夢啊……” “哼等缀!你這毒婦竟也來了?” 一聲冷哼從身側響起娇昙,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤尺迂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后冒掌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體枪狂,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年宋渔,在試婚紗的時候發(fā)現(xiàn)自己被綠了州疾。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡皇拣,死狀恐怖严蓖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情氧急,我是刑警寧澤颗胡,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站吩坝,受9級特大地震影響毒姨,放射性物質發(fā)生泄漏。R本人自食惡果不足惜钉寝,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一弧呐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嵌纲,春花似錦俘枫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至师溅,卻和暖如春茅信,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背墓臭。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工蘸鲸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人起便。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓棚贾,卻偏偏與公主長得像窖维,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子妙痹,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

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