Android Kotlin Coroutine(2):協(xié)程的啟動(dòng)以及協(xié)程上下文

前面入門(mén)時(shí)講過(guò)一個(gè)最簡(jiǎn)單的例子,通過(guò) GlobalScope.launch { } 可以啟動(dòng)一個(gè)協(xié)程,GlobalScope 可以簡(jiǎn)單理解為協(xié)程構(gòu)造者,它實(shí)際上是接口 CoroutineScope 的子類讶坯,那我們來(lái)看看它到底是什么,啟動(dòng)一個(gè)協(xié)程需要哪些關(guān)鍵要素岗屏。接下來(lái)我們講講協(xié)程相關(guān)的幾個(gè)主要類辆琅,先混個(gè)臉熟,心里有個(gè)大體概念之后这刷,再逐步深入婉烟。

1. CoroutineScope介紹

顧名思義“協(xié)程域”,只有它能創(chuàng)建協(xié)程暇屋,既然是創(chuàng)建者似袁,同樣它能管理它所創(chuàng)建的協(xié)程。該接口定義如下:

public interface CoroutineScope {
    /**
     * The context of this scope.
     * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
     * Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
     *
     * By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
     */
    public val coroutineContext: CoroutineContext
}

接口定義很簡(jiǎn)單咐刨,只包含一個(gè)叫 CoroutineContext 的參數(shù)昙衅,我們稱之為協(xié)程上下文今妄,那么這又是個(gè)什么鬼焰手?我們應(yīng)該在很多地方都見(jiàn)過(guò)名叫上下文的東西,例如在 Android 中一個(gè) Activity 就是上下文 Context 的子類瞳秽,由此可以類推 CoroutineContext 包含了協(xié)程運(yùn)行時(shí)的一些信息联予,具體后面再逐步介紹啼县。我們?cè)倏纯?GlobalScope 的定義:

public object GlobalScope : CoroutineScope {
    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext
}

原來(lái) GlobalScope 是個(gè)類似 Java 中的單例類,它的協(xié)程上下文是個(gè)空上下文 EmptyCoroutineContext沸久。那么協(xié)程的啟動(dòng)方法是在哪里定義的呢季眷,接口里我們好像沒(méi)見(jiàn)到。原來(lái)協(xié)程的啟動(dòng)方法都是通過(guò)擴(kuò)展函數(shù)來(lái)定義的卷胯,它的方法簽名為:

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job

從方法定義中可以看到子刮,協(xié)程的啟動(dòng)需要3個(gè)參數(shù):context(協(xié)程上下文)、start(協(xié)程啟動(dòng)模式)窑睁、block(協(xié)程體)挺峡,其中前2個(gè)參數(shù)都有默認(rèn)值,我們例子中的代碼其實(shí)只包含了協(xié)程體卵慰。協(xié)程上下文的概念很復(fù)雜沙郭,也特別難理解,我們可以將之類比為 Android 中的 Activity一樣裳朋。協(xié)程體就像 Thread.run() 方法中的代碼一樣病线,協(xié)程的運(yùn)行代碼都應(yīng)該寫(xiě)在里面吓著,這個(gè)很容易理解。該方法會(huì)返回一個(gè) Job 類型的對(duì)象送挑,有趣的是 Job 也是繼承自 CoroutineContext绑莺,可以認(rèn)為協(xié)程就是一個(gè)任務(wù)。

2. CoroutineStart(啟動(dòng)模式)介紹

CoroutineStart 是個(gè)枚舉類惕耕,其定義如下:

public enum class CoroutineStart {
    DEFAULT,
    LAZY,
    @ExperimentalCoroutinesApi
   ATOMIC,
   @ExperimentalCoroutinesApi
   UNDISPATCHED;
}

共定義了4種啟動(dòng)模式纺裁,但是后2種還是帶有實(shí)驗(yàn)性質(zhì)的 Api,我們分別用代碼來(lái)演示它們之間的區(qū)別司澎。

2.1 DEFAULT

這是默認(rèn)的啟動(dòng)模式欺缘,一旦 launch 方法調(diào)用后,立即開(kāi)始調(diào)度協(xié)程的執(zhí)行挤安。這種模式有點(diǎn)像線程調(diào)用 Thread.start() 方法之后谚殊,系統(tǒng)開(kāi)始調(diào)度線程的執(zhí)行一樣。當(dāng)調(diào)度 OK 之后蛤铜,協(xié)程體里的代碼會(huì)立即執(zhí)行嫩絮。

//方便打印出代碼執(zhí)行所在線程
fun log(o: Any?) {
    println("[${Thread.currentThread().name}]:$o")
}

GlobalScope.launch {
    log(1)
    val job = launch() {
        log(2)
    }
    log(3)
}

運(yùn)行結(jié)果可能為:

[DefaultDispatcher-worker-1]:1
[DefaultDispatcher-worker-1]:3
[DefaultDispatcher-worker-1]:2
2.2 LAZY

懶加載模式,launch 方法調(diào)用后围肥,并不會(huì)立即調(diào)度協(xié)程的執(zhí)行剿干。需要手動(dòng)調(diào)用,該協(xié)程才會(huì)開(kāi)始調(diào)度執(zhí)行穆刻。

GlobalScope.launch {
    log(1)
    val job = launch(start = CoroutineStart.LAZY) {
        log(2)
    }
    log(3)
}

同樣的代碼置尔,內(nèi)部的協(xié)程啟動(dòng)模式換成 LAZY 之后,再看執(zhí)行結(jié)果:

[DefaultDispatcher-worker-1]:1
[DefaultDispatcher-worker-1]:3

對(duì)比前面的代碼蛹批,能夠很明顯地看出 LAZYDEFAULT 的差別撰洗。

我們修改代碼為:

GlobalScope.launch {
    log(1)
    val job = launch(start = CoroutineStart.LAZY) {
        log(2)
    }
    job.join()  //等待協(xié)程的執(zhí)行結(jié)果篮愉,這里會(huì)觸發(fā)協(xié)程的調(diào)度執(zhí)行
    log(3)
}

運(yùn)行的結(jié)果為:

[DefaultDispatcher-worker-1]:1
[DefaultDispatcher-worker-1]:2
[DefaultDispatcher-worker-1]:3
2.3 ATOMIC

這種模式與 DEFAULT 類似腐芍,它也是一旦 launch 方法調(diào)用后,協(xié)程會(huì)立即開(kāi)始調(diào)度執(zhí)行试躏。但很有趣的是猪勇,在協(xié)程內(nèi)部沒(méi)有遇到掛起函數(shù)(suspend fun)之前,它不能取消掉颠蕴。

順便說(shuō)一下掛起函數(shù)泣刹,掛起函數(shù)是由 suspend 修飾的函數(shù),它只能在協(xié)程內(nèi)部或掛起函數(shù)內(nèi)調(diào)用犀被∫文可以簡(jiǎn)單理解為,它能"暫停"該函數(shù)的執(zhí)行寡键,當(dāng)然這里并不是真的暫停掀泳,只是說(shuō)協(xié)程調(diào)度器暫時(shí)不再調(diào)度該協(xié)程。

GlobalScope.launch {
    log(1)
    val job1 = launch(start = CoroutineStart.ATOMIC) {
        log(2)
        log(22)
    }
    job1.cancel()

    val job2 = launch {
        log(3)
        log(33)
    }
    job2.cancel()

    val job3 = launch(start = CoroutineStart.ATOMIC) {
        log(4)
        log(44)
        delay(100)
        log(444)
    }
    job3.cancel()

    val job4 = launch(start = CoroutineStart.ATOMIC) {
        delay(100)
        log(5)
    }
    job4.cancel()
}

這段代碼的執(zhí)行結(jié)果為:

[DefaultDispatcher-worker-1]:1
[DefaultDispatcher-worker-3]:2
[DefaultDispatcher-worker-3]:22
[DefaultDispatcher-worker-2]:4
[DefaultDispatcher-worker-2]:44

共創(chuàng)建了4個(gè)協(xié)程:job1、job2员舵、job3脑沿、job4,其中協(xié)程job2為默認(rèn)啟動(dòng)模式马僻,其他的啟動(dòng)模式都為 ATOMIC庄拇,delay(100) 是一個(gè)掛起函數(shù)調(diào)用,相當(dāng)于 Thread.sleep(100)的作用韭邓,每個(gè)協(xié)程創(chuàng)建之后立即調(diào)用 cancel() 方法取消執(zhí)行措近。我們來(lái)分析每個(gè)結(jié)果:

  • job1:協(xié)程體內(nèi)沒(méi)有調(diào)用掛起函數(shù),協(xié)程體內(nèi)的代碼都被執(zhí)行了女淑,該協(xié)程沒(méi)有被取消掉熄诡;
  • job2:協(xié)程被取消掉了;
  • job3:掛起函數(shù) delay(100) 之前的代碼執(zhí)行了诗力,掛起函數(shù)后面的代碼沒(méi)有執(zhí)行凰浮;
  • job4:協(xié)程體內(nèi)的第一行代碼就是掛起函數(shù)調(diào)用,最終該協(xié)程體內(nèi)的代碼都沒(méi)執(zhí)行苇本;

從上面的例子中可以看到袜茧,DEFAULT 模式啟動(dòng)的協(xié)程如果還沒(méi)調(diào)度執(zhí)行是可以取消掉的,ATOMIC 模式啟動(dòng)的協(xié)程如果還沒(méi)調(diào)度執(zhí)行時(shí)就被取消瓣窄,協(xié)程體內(nèi)第一個(gè)掛起函數(shù)之前的代碼依舊會(huì)執(zhí)行笛厦。如果該協(xié)程內(nèi)部沒(méi)有調(diào)用任何掛起函數(shù),則該協(xié)程里的代碼無(wú)論如何也會(huì)執(zhí)行俺夕。協(xié)程的取消有點(diǎn)像線程的中斷一樣裳凸,suspend 函數(shù)又有點(diǎn)像線程里能夠拋出中斷異常的方法一樣。

2.4 UNDISPATCHED

這種模式具備 ATOMIC 的功能劝贸,與之不同的是姨谷,一旦調(diào)用 launch 方法后,該協(xié)程會(huì)立即在當(dāng)前線程執(zhí)行映九。

GlobalScope.launch {
    log(1)
    val job1 = launch(start = CoroutineStart.UNDISPATCHED) {
        log(2)
        delay(100)
        log(22)
    }
    job1.cancel()

    val job2 = launch(start = CoroutineStart.UNDISPATCHED) {
        log(3)
        delay(100)
        log(33)
    }
    log("after job2")

    val job3 = launch(start = CoroutineStart.ATOMIC) {
        log(4)
        delay(100)
        log(44)
    }
    log("after job3")
}

執(zhí)行結(jié)果為:

[DefaultDispatcher-worker-2]:1
[DefaultDispatcher-worker-2]:2
[DefaultDispatcher-worker-2]:3
[DefaultDispatcher-worker-2]:after job2
[DefaultDispatcher-worker-2]:after job3
[DefaultDispatcher-worker-2]:4
[DefaultDispatcher-worker-2]:33
[DefaultDispatcher-worker-2]:44

job1 驗(yàn)證了它不能被取消的功能梦湘,job2 中 3 會(huì)立即在當(dāng)前線程執(zhí)行,所以 3 必然會(huì)在 after job2 之前執(zhí)行件甥,job3 中 4 會(huì)等待調(diào)度器調(diào)度執(zhí)行捌议,所以他并不會(huì)在 after job3 之前執(zhí)行,4after job3 的執(zhí)行順序?qū)嵸|(zhì)上與協(xié)程的調(diào)度來(lái)決定引有。

3. CoroutineContext介紹

根據(jù)文檔里的說(shuō)明瓣颅,CoroutineContext 的概念主要有3點(diǎn):

  1. It is an indexed set of [Element] instances. 它是一個(gè)包含 Element 實(shí)例的索引集;
  2. An indexed set is a mix between a set and a map. 索引集是 set 和 map 的混合結(jié)構(gòu)譬正;
  3. Every element in this set has a unique [Key]. 這個(gè)集合中的每個(gè)元素都有一個(gè)唯一的 Key宫补;

說(shuō)的通俗一點(diǎn)僻孝,CoroutineContext 就是一個(gè)集合 Collection,這個(gè)集合既有 set 的特性又有 map 的特性守谓,集合里的元素都是 Element 類型的穿铆,每個(gè) Element 類型的元素都有一個(gè)類型為 Key 的鍵。按慣例先來(lái)看看類定義:

public interface CoroutineContext {

    //操作符'[]'重載斋荞,通過(guò) Key 獲取 context 中的 Element 類型元素荞雏。可直接通過(guò) CoroutineContext[Key] 這種形式來(lái)獲取與 Key 關(guān)聯(lián)的元素平酿,類似從 List 中取出索引為 index 的某個(gè)元素:List[index]凤优,從 Map 中取出某個(gè)元素則為 Map.get(key)
    public operator fun <E : Element> get(key: Key<E>): E?

    //聚集函數(shù),函數(shù)式編程中出現(xiàn)比較多蜈彼,想象一下"菲波那切數(shù)列求和"就容易理解了
    //這里是提供了遍歷當(dāng)前 context 中所有 Element 元素的能力
    public fun <R> fold(initial: R, operation: (R, Element) -> R): R

    //操作符 '+' 重載筑辨,類似 List 中的 List.addAll(list)方法、Map 中的 Map.putAll(map) 方法幸逆,將2個(gè)集合合并成一個(gè)集合
    public operator fun plus(context: CoroutineContext): CoroutineContext

    //返回一個(gè)新的 context棍辕,但是該 conext 刪除了有指定 Key 的 Element。
    public fun minusKey(key: Key<*>): CoroutineContext
    
    //Key的定義还绘,空實(shí)現(xiàn)楚昭,僅僅只是做一個(gè)標(biāo)識(shí)
    public interface Key<E : Element>
    
    //Element的定義,同樣繼承自 CoroutineContext
    public interface Element : CoroutineContext {
    
        //每個(gè) Element 都有一個(gè) Key
        public val key: Key<*>
    
        public override operator fun <E : Element> get(key: Key<E>): E? =
            @Suppress("UNCHECKED_CAST")
            if (this.key == key) this as E else null
    
        public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
            operation(initial, this)
    
        public override fun minusKey(key: Key<*>): CoroutineContext =
            if (this.key == key) EmptyCoroutineContext else this
    }    
}

可以發(fā)現(xiàn)拍顷,CoroutineContext 感覺(jué)與 Java 里的 Map 最相似抚太,簡(jiǎn)直就是一個(gè)鍵為 Key 類型的 Map。眾所周知昔案,List尿贫、Map 的內(nèi)部數(shù)據(jù)結(jié)構(gòu)一般為數(shù)組、鏈表之類的踏揣,那么 CoroutineContext 的內(nèi)部數(shù)據(jù)結(jié)構(gòu)呢庆亡?

查看源碼,發(fā)現(xiàn)它的底層數(shù)據(jù)結(jié)構(gòu)是一個(gè)叫 CombinedContext 的類來(lái)實(shí)現(xiàn)的呼伸,這是一個(gè)內(nèi)部類身冀,定義如下:

internal class CombinedContext(
    private val left: CoroutineContext,
    private val element: Element
) : CoroutineContext, Serializable 

它有2個(gè)參數(shù),left 為 CoroutineContext 類型括享,element 為就是集合里的元素≌浯伲看到這個(gè)定義是不是特奇怪铃辖,既不像數(shù)組又不像鏈表,那么它是怎么具備集合的功能的呢猪叙,為此我寫(xiě)了個(gè)簡(jiǎn)單的例子:

class List<E> constructor() {
    private var head: E? = null
    private var tail: List<E>? = null

    constructor(head: E?, tail: List<E>?) : this() {
        this.head = head
        this.tail = tail
    }

    fun add(e: E) {
        if (head == null) {
            head = e
        } else {
            if (tail == null) {
                val nextList = List<E>()
                nextList.head = e
                tail = nextList
            } else {
                tail?.add(e)
            }
        }
    }

    fun size(): Int = (if (head == null) 0 else 1) + (tail?.size() ?: 0)

}

據(jù)說(shuō)這種叫做 List 的遞歸定義娇斩,有些函數(shù)式編程語(yǔ)言中仁卷,就是采用這種方式來(lái)定義 List 的。它有點(diǎn)像鏈表犬第,又跟鏈表不太一樣锦积,CombinedContext 與之非常類似,僅僅是頭尾位置換了一下歉嗓,當(dāng)然它更復(fù)雜丰介,我們?cè)賮?lái)看 plus 方法的具體實(shí)現(xiàn):

    public operator fun plus(context: CoroutineContext): CoroutineContext =
        if (context === EmptyCoroutineContext) this else
            context.fold(this) { acc, element ->
                val removed = acc.minusKey(element.key)
                if (removed === EmptyCoroutineContext) element else {
                    val interceptor = removed[ContinuationInterceptor]
                    if (interceptor == null) CombinedContext(removed, element) else {
                        val left = removed.minusKey(ContinuationInterceptor)
                        if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
                            CombinedContext(CombinedContext(left, element), interceptor)
                    }
                }
            }

整段代碼就是一遞歸的實(shí)現(xiàn),主要邏輯有:

  1. 除了少數(shù)情況外鉴分,主要返回的就是 CombinedContext 對(duì)象哮幢;
  2. 新返回的 CoroutineContext 對(duì)象,包含了 2 個(gè) context 里所包含的全部 Element 元素志珍;
  3. 在組合形成 CombinedContext 的時(shí)候橙垢,如果當(dāng)前 context 里有與要相加的 context 含有相同 Key 的 Element,則當(dāng)前 context 里的該元素會(huì)被刪除掉伦糯。這就讓 CoroutineContext 具備了 Set 的屬性柜某,一個(gè) Key,只能取出一個(gè)對(duì)應(yīng)的 Element敛纲;
  4. 這里有一個(gè)key為 ContinuationInterceptor 的元素莺琳,它也是繼承自 Element,通常叫做協(xié)程上下文攔截器(后面再單獨(dú)將它)载慈。它有點(diǎn)特殊惭等,不管多少次相加操作之后,它總是出現(xiàn)在最后面办铡。通過(guò)一個(gè) context辞做,我們總能最快找到攔截器(避免了遞歸查找);

下圖是主要的繼承了 CoroutineContext 的類圖:

CoroutineContext類繼承

下面我們來(lái)寫(xiě)個(gè)例子寡具,驗(yàn)證一下其中的特性:

val scope = MainScope()
val context = scope.coroutineContext
//取出 key 為 ContinuationInterceptor 的元素
println("interceptor: " + context[ContinuationInterceptor])

執(zhí)行結(jié)果為: interceptor: Main

class TestContext : ContinuationInterceptor {

    override val key: CoroutineContext.Key<*> = ContinuationInterceptor

    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        return continuation
    }
}

val scope = MainScope()
//執(zhí)行 context 的相加操作之后秤茅,再取出 key 為 ContinuationInterceptor 的元素
val context = scope.coroutineContext + TestContext()
println("interceptor: " + context[ContinuationInterceptor])

執(zhí)行結(jié)果為:interceptor: com.hjy.kotlinstudy.TestContext@18b8ff07

這里可以看到,context 的相加操作之后童叠,如果加號(hào)前后兩個(gè) context 都有相同的 key框喳,則最終只保留加號(hào)后面的 key 對(duì)應(yīng)的元素。如果這里你看到 context[ContinuationInterceptor] 方法調(diào)用厦坛,你一定會(huì)覺(jué)得很奇怪五垮,方括號(hào)里的參數(shù)應(yīng)該是一個(gè) Key 類型的對(duì)象啊,這里的 ContinuationInterceptor 只是一個(gè)繼承了 CoroutineContext 的接口啊杜秸,其實(shí)這只是 Kotlin 的一個(gè)特性放仗,在 ContinuationInterceptor 接口里定義了一個(gè)如下對(duì)象:

companion object Key : CoroutineContext.Key<ContinuationInterceptor>

這個(gè)俗稱伴生對(duì)象,context[ContinuationInterceptor] 等同于 context[ContinuationInterceptor.Key]撬碟,在 kotlin 里直接寫(xiě)類名等同于該類里的伴生對(duì)象诞挨,以后看到類似的寫(xiě)法也就不會(huì)覺(jué)得晦澀難懂了莉撇。

4. 小結(jié)

本文介紹了與協(xié)程啟動(dòng)相關(guān)的幾個(gè)主要類,特別是 CoroutineContext惶傻,我認(rèn)為它是協(xié)程的核心概念棍郎,理解它有助于真正理解協(xié)程的內(nèi)部運(yùn)行機(jī)制。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末银室,一起剝皮案震驚了整個(gè)濱河市涂佃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌粮揉,老刑警劉巖巡李,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異扶认,居然都是意外死亡侨拦,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)辐宾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)狱从,“玉大人,你說(shuō)我怎么就攤上這事叠纹〖狙校” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵誉察,是天一觀的道長(zhǎng)与涡。 經(jīng)常有香客問(wèn)我,道長(zhǎng)持偏,這世上最難降的妖魔是什么驼卖? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮鸿秆,結(jié)果婚禮上酌畜,老公的妹妹穿的比我還像新娘。我一直安慰自己卿叽,他們只是感情好桥胞,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著考婴,像睡著了一般贩虾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蕉扮,一...
    開(kāi)封第一講書(shū)人閱讀 52,475評(píng)論 1 312
  • 那天整胃,我揣著相機(jī)與錄音,去河邊找鬼喳钟。 笑死屁使,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的奔则。 我是一名探鬼主播蛮寂,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼易茬!你這毒婦竟也來(lái)了酬蹋?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤抽莱,失蹤者是張志新(化名)和其女友劉穎范抓,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體食铐,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡匕垫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了虐呻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片象泵。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖斟叼,靈堂內(nèi)的尸體忽然破棺而出偶惠,到底是詐尸還是另有隱情,我是刑警寧澤朗涩,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布忽孽,位于F島的核電站,受9級(jí)特大地震影響谢床,放射性物質(zhì)發(fā)生泄漏兄一。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一萤悴、第九天 我趴在偏房一處隱蔽的房頂上張望瘾腰。 院中可真熱鬧,春花似錦覆履、人聲如沸蹋盆。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)栖雾。三九已至,卻和暖如春伟众,著一層夾襖步出監(jiān)牢的瞬間析藕,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工凳厢, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留账胧,地道東北人竞慢。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像治泥,于是被迫代替她去往敵國(guó)和親筹煮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

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