三笑窜、線程基礎(chǔ)(二)正確停止線程

1、如何正確停止線程登疗?

1.1 原理介紹

通常情況下排截,不會手動停止一個線程,而是允許線程運(yùn)行到結(jié)束辐益,然后讓它自然停止断傲。但是依然會有許多特殊的情況需要提前停止線程,比如:用戶突然關(guān)閉程序智政,或程序運(yùn)行出錯重啟等艳悔。
在這種情況下,即將停止的線程在很多業(yè)務(wù)場景下仍然很有價(jià)值女仰。尤其是想要寫一個健壯性很好猜年,能夠安全應(yīng)對各種場景的程序時抡锈,正確停止線程就顯得格外重要。但是Java 并沒有提供簡單易用乔外,能夠直接安全停止線程的能力床三。

1.2 為什么不強(qiáng)制停止?而是通知杨幼、協(xié)作

對于 Java 而言撇簿,最正確的停止線程的方式是使用 interrupt。但 interrupt 僅僅起到通知被停止線程的作用差购。而對于被停止的線程而言四瘫,它擁有完全的自主權(quán),它既可以選擇立即停止欲逃,也可以選擇一段時間后停止找蜜,也可以選擇壓根不停止。
那么為什么 Java 不提供強(qiáng)制停止線程的能力呢稳析?
事實(shí)上洗做,Java 希望程序間能夠相互通知、相互協(xié)作地管理線程彰居,因?yàn)槿绻涣私鈱Ψ秸谧龅墓ぷ鞒现剑Q(mào)然強(qiáng)制停止線程就可能會造成一些安全的問題,為了避免造成問題就需要給對方一定的時間來整理收尾工作陈惰。比如:線程正在寫入一個文件畦徘,這時收到終止信號,它就需要根據(jù)自身業(yè)務(wù)判斷抬闯,是選擇立即停止井辆,還是將整個文件寫入成功后停止,而如果選擇立即停止就可能造成數(shù)據(jù)不完整画髓,不管是中斷命令發(fā)起者掘剪,還是接收者都不希望數(shù)據(jù)出現(xiàn)問題平委。

1.3 如何用 interrupt 停止線程?

while (!Thread.currentThread().islnterrupted() && more work to do) {
    do more work
}

明白 Java 停止線程的設(shè)計(jì)原則之后奈虾,看看如何用代碼實(shí)現(xiàn)停止線程的邏輯。
一旦調(diào)用某個線程的 interrupt() 之后廉赔,這個線程的中斷標(biāo)記位就會被設(shè)置成 true肉微。每個線程都有這樣的標(biāo)記位,當(dāng)線程執(zhí)行時蜡塌,應(yīng)該定期檢查這個標(biāo)記位碉纳,如果標(biāo)記位被設(shè)置成 true,就說明有程序想終止該線程馏艾。
回到源碼劳曹,可以看到在 while 循環(huán)體判斷語句中奴愉,首先通過 Thread.currentThread().isInterrupt()判斷線程是否被中斷,隨后檢查是否還有工作要做铁孵。&& 邏輯表示只有當(dāng)兩個判斷條件同時滿足的情況下锭硼,才會去執(zhí)行下面的工作。

再看看具體例子蜕劝。

public class StopThread implements Runnable {
 
    @Override
    public void run() {
        int count = 0;
        while (!Thread.currentThread().isInterrupted() && count < 1000) {
            System.out.println("count = " + count++);
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThread());
        thread.start();
        Thread.sleep(5);
        thread.interrupt();
    }
}

在 StopThread 類的 run() 方法中檀头,首先判斷線程是否被中斷,然后判斷 count 值是否小于 1000岖沛。這個線程的工作內(nèi)容很簡單暑始,就是打印 0~999 的數(shù)字,每打印一個數(shù)字 count 值加 1婴削,可以看到廊镜,線程會在每次循環(huán)開始之前,檢查是否被中斷了馆蠕。接下來在 main 函數(shù)中會啟動該線程期升,然后休眠 5 毫秒后立刻中斷線程,該線程會檢測到中斷信號互躬,于是在還沒打印完1000個數(shù)的時候就會停下來播赁,這種就屬于通過 interrupt 正確停止線程的情況。

1.4 sleep 期間能否感受到中斷?

Runnable runnable = () -> {
    int num = 0;
    try {
        while (!Thread.currentThread().isInterrupted() && 
        num <= 1000) {
            System.out.println(num);
            num++;
            Thread.sleep(1000000);
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
};

那么考慮一種特殊情況吼渡,改寫上面的代碼容为,如果線程在執(zhí)行任務(wù)期間有休眠需求,也就是每打印一個數(shù)字寺酪,就進(jìn)入一次 sleep 坎背,而此時將 Thread.sleep() 的休眠時間設(shè)置為 1000 秒鐘。

public class StopDuringSleep {
 
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            try {
                while (!Thread.currentThread().isInterrupted() && num <= 1000) {
                    System.out.println(num);
                    num++;
                    Thread.sleep(1000000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5);
        thread.interrupt();
    }
}

主線程休眠 5 毫秒后寄雀,通知子線程中斷得滤,此時子線程仍在執(zhí)行 sleep 語句,處于休眠中盒犹。那么就需要考慮一點(diǎn)懂更,在休眠中的線程是否能夠感受到中斷通知呢?是否需要等到休眠結(jié)束后才能中斷線程呢急膀?如果是這樣沮协,就會帶來嚴(yán)重的問題,因?yàn)轫憫?yīng)中斷太不及時了卓嫂。正因?yàn)槿绱丝对荩琂ava 設(shè)計(jì)者在設(shè)計(jì)之初就考慮到了這一點(diǎn)。

如果 sleep晨雳、wait 等可以讓線程進(jìn)入阻塞的方法使線程休眠了行瑞,而處于休眠中的線程被中斷奸腺,那么線程是可以感受到中斷信號的,并且會拋出一個 InterruptedException 異常血久,同時清除中斷信號洋机,將中斷標(biāo)記位設(shè)置成 false。這樣一來就不用擔(dān)心長時間休眠中線程感受不到中斷了洋魂,因?yàn)榧幢憔€程還在休眠绷旗,仍然能夠響應(yīng)中斷通知,并拋出異常副砍。

1.5 兩種最佳處理方式

在實(shí)際開發(fā)中肯定是團(tuán)隊(duì)協(xié)作的衔肢,不同的人負(fù)責(zé)編寫不同的方法,然后相互調(diào)用來實(shí)現(xiàn)整個業(yè)務(wù)的邏輯豁翎。那么如果我們負(fù)責(zé)編寫的方法需要被別人調(diào)用角骤,同時我們的方法內(nèi)調(diào)用了 sleep 或者 wait 等能響應(yīng)中斷的方法時,僅僅 catch 住異常是不夠的心剥。

void subTas() {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // 在這里不處理該異常是非常不好的
    }
}

我們可以在方法中使用 try/catch 或在方法簽名中聲明throws InterruptedException邦尊。

方式一:方法簽名拋異常,run() 強(qiáng)制 try/catch
我們先來看下 try/catch 的處理邏輯优烧。如上面的代碼所示蝉揍,catch 語句塊里代碼是空的,它并沒有進(jìn)行任何處理畦娄。假設(shè)線程執(zhí)行到這個方法又沾,并且正在 sleep,此時有線程發(fā)送 interrupt 通知試圖中斷線程熙卡,就會立即拋出異常杖刷,并清除中斷信號。拋出的異常被 catch 語句塊捕捉驳癌。

但是滑燃,捕捉到異常的 catch 沒有進(jìn)行任何處理邏輯,相當(dāng)于把中斷信號給隱藏了颓鲜,這樣做是非常不合理的表窘,那么究竟應(yīng)該怎么處理呢?首先灾杰,可以選擇在方法簽名中拋出異常蚊丐。

void subTask2() throws InterruptedException {
    Thread.sleep(1000);
}

正如代碼所示熙参,要求每一個方法的調(diào)用方有義務(wù)去處理異常艳吠。調(diào)用方要不使用 try/catch 并在 catch 中正確處理異常,要不將異常聲明到方法簽名中孽椰。如果每層邏輯都遵守規(guī)范昭娩,便可以將中斷信號層層傳遞到頂層凛篙,最終讓 run() 方法可以捕獲到異常。而對于 run() 方法而言栏渺,它本身沒有拋出 checkedException 的能力呛梆,只能通過 try/catch 來處理異常。層層傳遞異常的邏輯保障了異常不會被遺漏磕诊,而對 run() 方法而言填物,就可以根據(jù)不同的業(yè)務(wù)邏輯來進(jìn)行相應(yīng)的處理。

方式二:再次中斷

private void reInterrupt() {
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        e.printStackTrace();
    }
}

除了剛才推薦的將異常聲明到方法簽名中的方式外霎终,還可以在 catch 語句中再次中斷線程滞磺。如代碼所示,需要在 catch 語句塊中調(diào)用 Thread.currentThread().interrupt() 函數(shù)莱褒。因?yàn)槿绻€程在休眠期間被中斷击困,那么會自動清除中斷信號。如果這時手動添加中斷信號广凸,中斷信號依然可以被捕捉到阅茶。這樣后續(xù)執(zhí)行的方法依然可以檢測到這里發(fā)生過中斷,可以做出相應(yīng)的處理谅海,整個線程可以正常退出脸哀。

需要注意,在實(shí)際開發(fā)中不能盲目吞掉中斷扭吁,如果不在方法簽名中聲明企蹭,也不在 catch 語句塊中再次恢復(fù)中斷,而是在 catch 中不作處理智末,稱這種行為是“屏蔽了中斷請求”橄杨。如果盲目地屏蔽了中斷請求玖翅,會導(dǎo)致中斷信號被完全忽略,最終導(dǎo)致線程無法正確停止。

2笋籽、為什么 volatile 標(biāo)記位的停止方法是錯誤的?

2.1 錯誤的停止方法

首先啊楚,來看幾種停止線程的錯誤方法帮碰。比如 stop(),suspend() 和 resume()尼酿,這些方法已經(jīng)被 Java 直接標(biāo)記為 @Deprecated爷狈。如果再調(diào)用這些方法,IDE 會友好地提示裳擎,我們不應(yīng)該再使用它們了涎永。
但為什么它們不能使用了呢?是因?yàn)?stop() 會直接把線程停止,這樣就沒有給線程足夠的時間來處理想要在停止前保存數(shù)據(jù)的邏輯羡微,任務(wù)戛然而止谷饿,會導(dǎo)致出現(xiàn)數(shù)據(jù)完整性等問題。
而對于 suspend() 和 resume() 而言妈倔,它們的問題在于如果線程調(diào)用 suspend()博投,它并不會釋放鎖,就開始進(jìn)入休眠盯蝴,但此時有可能仍持有鎖毅哗,這樣就容易導(dǎo)致死鎖問題,因?yàn)檫@把鎖在線程被 resume() 之前捧挺,是不會被釋放的黎做。正是因?yàn)橛羞@樣的風(fēng)險(xiǎn),所以 suspend() 和 resume() 組合使用的方法也被廢棄了松忍。

死鎖舉例:
假設(shè)線程 A 調(diào)用了 suspend() 方法讓線程 B 掛起蒸殿,線程 B 進(jìn)入休眠,而線程 B 又剛好持有一把鎖鸣峭,此時假設(shè)線程 A 想訪問線程 B 持有的鎖宏所,但線程 B 并沒有釋放鎖就進(jìn)入休眠等待喚醒,而對于線程 A 而言摊溶,又不會喚醒線程 B爬骤,除非拿到鎖,這就造成了死鎖問題莫换。

2.2 volatile 修飾標(biāo)記位適用的場景

public class VolatileCanStop implements Runnable {
 
    private volatile boolean canceled = false;
 
    @Override
    public void run() {
        int num = 0;
        try {
            while (!canceled && num <= 1000000) {
                if (num % 10 == 0) {
                    System.out.println(num + "是10的倍數(shù)霞玄。");
                }
                num++;
                Thread.sleep(1);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        VolatileCanStop r = new VolatileCanStop();
        Thread thread = new Thread(r);
        thread.start();
        Thread.sleep(3000);
        r.canceled = true;
    }
}

什么場景下 volatile 修飾標(biāo)記位可以讓線程正常停止呢?如代碼所示拉岁,聲明了一個叫作 VolatileCanStop 的類坷剧, 它實(shí)現(xiàn)了 Runnable 接口,然后在 run() 中進(jìn)行 while 循環(huán)喊暖,在循環(huán)體中又進(jìn)行了兩層判斷惫企,首先判斷 canceled 變量的值,canceled 變量是一個被 volatile 修飾的初始值為 false 的布爾值陵叽,當(dāng)該值變?yōu)?true 時狞尔,while 跳出循環(huán),while 的第二個判斷條件是 num 值小于1000000(一百萬)巩掺,在while 循環(huán)體里偏序,只要是 10 的倍數(shù)就打印出來,然后 num++胖替。

接下來研儒,首先啟動線程豫缨,然后經(jīng)過 3 秒鐘的時間,把用 volatile 修飾的布爾值的標(biāo)記位設(shè)置成 true殉摔,這樣,正在運(yùn)行的線程就會在下一次 while 循環(huán)中判斷出 canceled 的值已經(jīng)變成 true 了记焊,這樣就不再滿足 while 的判斷條件逸月,跳出整個 while 循環(huán),線程就停止了遍膜,這種情況是演示 volatile 修飾的標(biāo)記位可以正常工作的情況碗硬,但是如果說某個方法是正確的,那么它應(yīng)該不僅僅是在一種情況下適用瓢颅,而在其他情況下也應(yīng)該是適用的恩尾。

2.3 volatile 修飾標(biāo)記位不適用的場景

接下來就用一個生產(chǎn)者/消費(fèi)者模式的案例來演示為什么說 volatile 標(biāo)記位的停止方法是不完美的。

class Producer implements Runnable {
    public volatile boolean canceled = false;
    BlockingQueue storage;
    public Producer(BlockingQueue storage) {
        this.storage = storage;
    }
 
    @Override
    public void run() {
        int num = 0;
        try {
            while (num <= 100000 && !canceled) {
                if (num % 50 == 0) {
                    storage.put(num);
                    System.out.println(num + "是50的倍數(shù),被放到倉庫中了挽懦。");
                }
                num++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("生產(chǎn)者結(jié)束運(yùn)行");
        }
    }
}

首先翰意,聲明了一個生產(chǎn)者 Producer,通過 volatile 標(biāo)記的初始值為 false 的布爾值 canceled 來停止線程信柿。而在 run() 方法中冀偶,while 的判斷語句是 num 是否小于 100000 及 canceled 是否被標(biāo)記。while 循環(huán)體中判斷 num 如果是 50 的倍數(shù)就放到 storage 倉庫中渔嚷,storage 是生產(chǎn)者與消費(fèi)者之間進(jìn)行通信的存儲器进鸠,當(dāng) num 大于 100000 或被通知停止時,會跳出 while 循環(huán)并執(zhí)行 finally 語句塊形病,告訴大家“生產(chǎn)者結(jié)束運(yùn)行”客年。

class Consumer {
    BlockingQueue storage;
    public Consumer(BlockingQueue storage) {
        this.storage = storage;
    }
    public boolean needMoreNums() {
        if (Math.random() > 0.97) {
            return false;
        }
        return true;
    }
}

而對于消費(fèi)者 Consumer,它與生產(chǎn)者共用同一個倉庫 storage漠吻,并且在方法內(nèi)通過 needMoreNums() 方法判斷是否需要繼續(xù)使用更多的數(shù)字量瓜,剛才生產(chǎn)者生產(chǎn)了一些 50 的倍數(shù)供消費(fèi)者使用,消費(fèi)者是否繼續(xù)使用數(shù)字的判斷條件是產(chǎn)生一個隨機(jī)數(shù)并與 0.97 進(jìn)行比較途乃,大于 0.97 就不再繼續(xù)使用數(shù)字榔至。

public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue storage = new ArrayBlockingQueue(8);

        Producer producer = new Producer(storage);
        Thread producerThread = new Thread(producer);
        producerThread.start();
        Thread.sleep(500);

        Consumer consumer = new Consumer(storage);
        while (consumer.needMoreNums()) {
            System.out.println(consumer.storage.take() + "被消費(fèi)了");
            Thread.sleep(100);
        }
        System.out.println("消費(fèi)者不需要更多數(shù)據(jù)了。");

        //一旦消費(fèi)不需要更多數(shù)據(jù)了欺劳,我們應(yīng)該讓生產(chǎn)者也停下來唧取,但是實(shí)際情況卻停不下來
        producer.canceled = true;
        System.out.println(producer.canceled);
    }
}

下面來看下 main 函數(shù),首先創(chuàng)建了生產(chǎn)者/消費(fèi)者共用的倉庫 BlockingQueue storage划提,倉庫容量是 8枫弟,并且建立生產(chǎn)者并將生產(chǎn)者放入線程后啟動線程,啟動后進(jìn)行 500 毫秒的休眠鹏往,休眠時間保障生產(chǎn)者有足夠的時間把倉庫塞滿淡诗,而倉庫達(dá)到容量后就不會再繼續(xù)往里塞骇塘,這時生產(chǎn)者會阻塞,500 毫秒后消費(fèi)者也被創(chuàng)建出來韩容,并判斷是否需要使用更多的數(shù)字款违,然后每次消費(fèi)后休眠 100 毫秒,這樣的業(yè)務(wù)邏輯是有可能出現(xiàn)在實(shí)際生產(chǎn)中的群凶。

當(dāng)消費(fèi)者不再需要數(shù)據(jù)插爹,就會將 canceled 的標(biāo)記位設(shè)置為 true,理論上此時生產(chǎn)者會跳出 while 循環(huán)请梢,并打印輸出“生產(chǎn)者運(yùn)行結(jié)束”赠尾。

然而結(jié)果卻不是我們想象的那樣,盡管已經(jīng)把 canceled 設(shè)置成 true毅弧,但生產(chǎn)者仍然沒有停止气嫁,這是因?yàn)樵谶@種情況下,生產(chǎn)者在執(zhí)行 storage.put(num) 時發(fā)生阻塞够坐,在它被叫醒之前是沒有辦法進(jìn)入下一次循環(huán)判斷 canceled 的值的寸宵,所以在這種情況下用 volatile 是沒有辦法讓生產(chǎn)者停下來的,相反如果用 interrupt 語句來中斷元咙,即使生產(chǎn)者處于阻塞狀態(tài)邓馒,仍然能夠感受到中斷信號,并做響應(yīng)處理蛾坯。

3光酣、總結(jié)

(1)如何正確停止線程?
首先,從原理上講應(yīng)該用 interrupt 來請求中斷脉课,而不是強(qiáng)制停止救军,因?yàn)檫@樣可以避免數(shù)據(jù)錯亂,也可以讓線程有時間結(jié)束收尾工作倘零。

(2)如果我們是子方法的編寫者唱遭,遇到了 interruptedException,該如何處理呈驶?
可以把異常聲明在方法中拷泽,以便頂層方法可以感知捕獲到異常,或者也可以在 catch 中再次聲明中斷袖瞻,這樣下次循環(huán)也可以感知中斷司致,所以要想正確停止線程就要求我們停止方,被停止方聋迎,子方法的編寫者相互配合脂矫,大家都按照一定的規(guī)范來編寫代碼,就可以正確地停止線程了霉晕。

(3)有哪些方法是不夠好的庭再?
比如說已經(jīng)被舍棄的 stop()捞奕、suspend() 和 resume(),它們由于有很大的安全風(fēng)險(xiǎn)比如死鎖風(fēng)險(xiǎn)而被舍棄拄轻,而 volatile 這種方法在某些特殊的情況下颅围,比如線程被長時間阻塞的情況,就無法及時感受中斷恨搓,所以 volatile 是不夠全面的停止線程的方法院促。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市奶卓,隨后出現(xiàn)的幾起案子一疯,更是在濱河造成了極大的恐慌撼玄,老刑警劉巖夺姑,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異掌猛,居然都是意外死亡盏浙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門荔茬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來废膘,“玉大人,你說我怎么就攤上這事慕蔚∝せ疲” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵孔飒,是天一觀的道長灌闺。 經(jīng)常有香客問我,道長坏瞄,這世上最難降的妖魔是什么桂对? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮鸠匀,結(jié)果婚禮上蕉斜,老公的妹妹穿的比我還像新娘。我一直安慰自己缀棍,他們只是感情好宅此,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著爬范,像睡著了一般诽凌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上坦敌,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天侣诵,我揣著相機(jī)與錄音痢法,去河邊找鬼。 笑死杜顺,一個胖子當(dāng)著我的面吹牛财搁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播躬络,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼尖奔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了穷当?” 一聲冷哼從身側(cè)響起提茁,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎馁菜,沒想到半個月后茴扁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡汪疮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年峭火,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片智嚷。...
    茶點(diǎn)故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡卖丸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出盏道,到底是詐尸還是另有隱情稍浆,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布猜嘱,位于F島的核電站衅枫,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏泉坐。R本人自食惡果不足惜为鳄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望腕让。 院中可真熱鬧孤钦,春花似錦、人聲如沸纯丸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽觉鼻。三九已至俊扭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間坠陈,已是汗流浹背萨惑。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工捐康, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人庸蔼。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓解总,卻偏偏與公主長得像,于是被迫代替她去往敵國和親姐仅。 傳聞我的和親對象是個殘疾皇子花枫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評論 2 359