Android多線程開發(fā)

參考鏈接:

多線程的三種實(shí)現(xiàn)方式

  1. 繼承Thread類,重寫run函數(shù)方法
class MyThread extends Thread {

    @Override
    public void run() {
        super.run();
    }
}
  1. 實(shí)現(xiàn)Runnable接口,重寫run函數(shù)方法
class MyThread implements Runnable{

    @Override
    public void run() {
        
    }
}
  1. 實(shí)現(xiàn)Callable接口,重寫call函數(shù)方法,ExecutorService、Callable购裙、Future實(shí)現(xiàn)有返回結(jié)果的多線程
class MyThread <T> implements Callable<T> {

    @Override
    public T call() {
        return null;
    }
}
  1. Callable和Runnable的不同之處:

①Callable規(guī)定的方法是call(),而Runnable規(guī)定的方法是run().
②Callable的任務(wù)執(zhí)行后可返回值,而Runnable的任務(wù)是不能返回值的
③call()方法可拋出異常坯台,而run()方法是不能拋出異常的。
④運(yùn)行Callable任務(wù)可拿到一個(gè)Future對(duì)象瘫寝,F(xiàn)uture表示異步計(jì)算的結(jié)果蜒蕾。通過Future對(duì)象可了解任務(wù)執(zhí)行情況,可取消任務(wù)的執(zhí)行。

Callable和Runable詳解見鏈接:

Android(Java)之多線程結(jié)果返回——Future 焕阿、FutureTask咪啡、Callable、Runnable

如何停止一個(gè)線程

  1. 創(chuàng)建一個(gè)標(biāo)識(shí)(flag)暮屡,當(dāng)線程完成你所需要的工作后瑟匆,可以將標(biāo)識(shí)設(shè)置為退出標(biāo)識(shí)
  2. 使用Thread的interrupt()方法和nterrupted()方法,兩者配合break退出循環(huán)栽惶,或者return來(lái)停止線程愁溜,有點(diǎn)類似標(biāo)識(shí)(flag)
  3. 可以使用try-catch語(yǔ)句,在try-catch語(yǔ)句中拋出異常外厂,強(qiáng)行停止線程進(jìn)入catch語(yǔ)句冕象,這種方法可以將錯(cuò)誤向上拋,使線程停止事件得以傳播

Thread線程狀態(tài)和相關(guān)方法

Thread
  1. 可運(yùn)行(runnable):線程對(duì)象創(chuàng)建后汁蝶,線程調(diào)用start()方法渐扮。該狀態(tài)的線程位于可運(yùn)行線程池中论悴,等待被線程調(diào)度選中,獲取cpu的使用權(quán)
  2. 運(yùn)行(running):可運(yùn)行狀態(tài)(runnable)的線程獲得了cpu使用權(quán)墓律,執(zhí)行程序代碼
  3. 阻塞(block):線程因?yàn)槟撤N原因放棄了cpu使用權(quán)膀估,即讓出了cpu使用權(quán),暫時(shí)停止運(yùn)行耻讽,直到線程進(jìn)入可運(yùn)行(runnable)狀態(tài)察纯,才有機(jī)會(huì)再次獲得cpu使用權(quán)轉(zhuǎn)到運(yùn)行(running)狀態(tài)。阻塞的情況分三種:
    (1)等待阻塞:運(yùn)行(running)的線程執(zhí)行o.wait()方法针肥,JVM會(huì)把該線程放入等待隊(duì)列(waitting queue)中
    (2)同步阻塞:運(yùn)行(running)的線程在獲取對(duì)象的同步鎖時(shí)饼记,若該同步鎖被別的線程占用,則JVM會(huì)把該線程放入鎖池(lock pool)中
    其他阻塞:運(yùn)行(running)的線程執(zhí)行Thread.sleep(long ms)或t.join()方法慰枕,或者發(fā)出了I/O請(qǐng)求時(shí)具则,JVM會(huì)把該線程置為阻塞狀態(tài)。當(dāng)sleep()狀態(tài)超時(shí)具帮、join()等待線程終止或者超時(shí)博肋、或者I/O處理完畢時(shí),線程重新轉(zhuǎn)入可運(yùn)行(runnable)狀態(tài)
    (3)死亡(dead):線程run()蜂厅、main() 方法執(zhí)行結(jié)束束昵,或者因異常退出了run()方法,則該線程結(jié)束生命周期葛峻,且死亡的線程不可再次復(fù)生

sleep和wait的區(qū)別

  • sleep()是Thread類的方法,wait()是Object類中的方法;
  • 調(diào)用sleep()锹雏,在指定的時(shí)間里,暫停程序的執(zhí)行术奖,讓出CPU給其他線程礁遵,當(dāng)超過時(shí)間的限制后,又重新恢復(fù)到運(yùn)行狀態(tài)采记,在這個(gè)過程中佣耐,線程不會(huì)釋放對(duì)象鎖;調(diào)用wait()時(shí),線程會(huì)釋放對(duì)象鎖唧龄,進(jìn)入此對(duì)象的等待鎖池中兼砖,只有此對(duì)象調(diào)用notify()時(shí),線程進(jìn)入運(yùn)行狀態(tài)
    方法介紹:
  1. wait() :使一個(gè)線程處于等待狀態(tài)既棺,并且釋放所有持有對(duì)象的lock鎖讽挟,直到notify()/notifyAll()被喚醒后放到鎖定池(lock blocked pool ),釋放同步鎖使線 程回到可運(yùn)行狀態(tài)(Runnable)丸冕。
  2. sleep():使一個(gè)線程處于睡眠狀態(tài)耽梅,是一個(gè)靜態(tài)方法,調(diào)用此方法要捕捉Interrupted異常胖烛,醒來(lái)后進(jìn)入runnable狀態(tài)眼姐,等待JVM調(diào)度诅迷。
  3. notify():使一個(gè)等待狀態(tài)的線程喚醒,注意并不能確切喚醒等待狀態(tài)線程众旗,是由JVM決定且不按優(yōu)先級(jí)罢杉。
  4. notifyAll():使所有等待狀態(tài)的線程喚醒,注意并不是給所有線程上鎖贡歧,而是讓它們競(jìng)爭(zhēng)滩租。
  5. join():使一個(gè)線程中斷,IO完成會(huì)回到Runnable狀態(tài)艘款,等待JVM的調(diào)度。
  6. Synchronized():使Running狀態(tài)的線程加同步鎖使其進(jìn)入(lock blocked pool ),同步鎖被釋放進(jìn)入可運(yùn)行狀態(tài)(Runnable)沃琅。

如何實(shí)現(xiàn)線程同步

1. Synchronized方法

當(dāng)用此關(guān)鍵字修飾方法時(shí)哗咆, 內(nèi)置鎖會(huì)保護(hù)整個(gè)方法。在調(diào)用該方法前益眉,需要獲得內(nèi)置鎖晌柬,否則就處于阻塞狀態(tài)。注: synchronized關(guān)鍵字也可以修飾靜態(tài)方法郭脂,此時(shí)如果調(diào)用該靜態(tài)方法年碘,將會(huì)鎖住整個(gè)類。

  • 同步方法:給一個(gè)方法增加synchronized修飾符之后就可以使它成為同步方法展鸡,這個(gè)方法可以是靜態(tài)方法和非靜態(tài)方法屿衅,但是不能是抽象類的抽象方法,也不能是接口中的接口方法莹弊。當(dāng)任意一個(gè)線程進(jìn)入到一個(gè)對(duì)象的任意一個(gè)同步方法時(shí)涤久,這個(gè)對(duì)象的所有同步方法都被鎖定了,在此期間忍弛,其他任何線程都不能訪問這個(gè)對(duì)象的任意一個(gè)同步方法响迂,直到這個(gè)線程執(zhí)行完它所調(diào)用的同步方法并從中退出,從而導(dǎo)致它釋放了該對(duì)象的同步鎖之后细疚。在一個(gè)對(duì)象被某個(gè)線程鎖定之后蔗彤,其他線程是可以訪問這個(gè)對(duì)象的所有非同步方法的。
  • 同步塊:同步塊是通過鎖定一個(gè)指定的對(duì)象疯兼,來(lái)對(duì)同步塊中包含的代碼進(jìn)行同步然遏;而同步方法是對(duì)這個(gè)方法塊里的代碼進(jìn)行同步,而這種情況下鎖定的對(duì)象就是同步方法所屬的主體對(duì)象自身吧彪。如果這個(gè)方法是靜態(tài)同步方法呢啦鸣?那么線程鎖定的就不是這個(gè)類的對(duì)象了,也不是這個(gè)類自身来氧,而是這個(gè)類對(duì)應(yīng)的java.lang.Class類型的對(duì)象诫给。同步方法和同步塊之間的相互制約只限于同一個(gè)對(duì)象之間香拉,所以靜態(tài)同步方法只受它所屬類的其它靜態(tài)同步方法的制約,而跟這個(gè)類的實(shí)例(對(duì)象)沒有關(guān)系中狂。
    如果一個(gè)對(duì)象既有同步方法凫碌,又有同步塊,那么當(dāng)其中任意一個(gè)同步方法或者同步塊被某個(gè)線程執(zhí)行時(shí)胃榕,這個(gè)對(duì)象就被鎖定了盛险,其他線程無(wú)法在此時(shí)訪問這個(gè)對(duì)象的同步方法,也不能執(zhí)行同步塊勋又。synchronized 關(guān)鍵字用于保護(hù)共享數(shù)據(jù)苦掘。

2. 使用特殊域變量(volatile)實(shí)現(xiàn)線程同步

volatile關(guān)鍵字為域變量的訪問提供了一種免鎖機(jī)制,相當(dāng)于告訴虛擬機(jī)該域可能會(huì)被其他線程更新楔壤,因此每次使用該域就要重新計(jì)算鹤啡,而不是使用寄存器中的值,不能用來(lái)修飾final類型的變量

3. 使用重入鎖ReentrantLock類實(shí)現(xiàn)線程同步

ReentrantLock類是可重入蹲嚣、互斥递瑰、實(shí)現(xiàn)了Lock接口的鎖,方法:
ReentrantLock() : 創(chuàng)建一個(gè)ReentrantLock實(shí)例
lock() : 獲得鎖
unlock() : 釋放鎖隙畜,通常在finally代碼釋放鎖

private Lock lock = new ReentrantLock();
public void save(int money) {
                lock.lock();
                try{
                    account += money;
                }finally{
                    lock.unlock();
                }

            }

4. 使用ThreadLocal局部變量實(shí)現(xiàn)線程同步

如果使用ThreadLocal管理變量抖部,則每一個(gè)使用該變量的線程都獲得該變量的副本,副本之間相互獨(dú)立议惰,這樣每一個(gè)線程都可以隨意修改自己的變量副本慎颗,而不會(huì)對(duì)其他線程產(chǎn)生影響。

 private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
                @Override
                protected Integer initialValue(){
                    return 100;
                }
            };

ThreadLocal與同步機(jī)制都是為了解決多線程中相同變量的訪問沖突問題言询,前者采用以”空間換時(shí)間”的方法哗总,后者采用以”時(shí)間換空間”的方式

5. 使用阻塞隊(duì)列LinkedBlockingQueue實(shí)現(xiàn)線程同步

LinkedBlockingQueue是一個(gè)基于已連接節(jié)點(diǎn)的,范圍任意的blocking queue倍试。 隊(duì)列是先進(jìn)先出的順序(FIFO
LinkedBlockingQueue 類常用方法:
LinkedBlockingQueue() : 創(chuàng)建一個(gè)容量為Integer.MAX_VALUE 的 LinkedBlockingQueue
put(E e) : 在隊(duì)尾添加一個(gè)元素讯屈,如果隊(duì)列滿則阻塞
size() : 返回隊(duì)列中的元素個(gè)數(shù)
take() : 移除并返回隊(duì)頭元素,如果隊(duì)列空則阻塞
代碼實(shí)例: 實(shí)現(xiàn)商家生產(chǎn)商品和買賣商品的同步
當(dāng)隊(duì)列滿時(shí):
  add()方法會(huì)拋出異常
  offer()方法返回false
  put()方法會(huì)阻塞

6. 使用原子變量AtomicXxx實(shí)現(xiàn)線程同步

Xxx 可以是 String ,Integer等
原子操作就是指將讀取變量值县习、修改變量值涮母、保存變量值看成一個(gè)整體來(lái)操作即-這幾種行為要么同時(shí)完成,要么都不完成躁愿。
AtomicInteger類常用方法:
AtomicInteger(int initialValue) : 創(chuàng)建具有給定初始值的新的AtomicInteger
addAddGet(int dalta) : 以原子方式將給定值與當(dāng)前值相加
get() : 獲取當(dāng)前值
原子操作主要有:
對(duì)于引用變量和大多數(shù)原始變量(long和double除外)的讀寫操作叛本;  
對(duì)于所有使用volatile修飾的變量(包括long和double)的讀寫操作。

7. 使用線程池進(jìn)行管理及優(yōu)化

(1). Android HandlerThread

public class HandlerThreadActivity extends AppCompatActivity{

private HandlerThread mCheckMsgThread;
    private Handler mCheckMsgHandler;
    //與UI線程管理的handler
    private Handler mHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_thread_handler);

        //創(chuàng)建后臺(tái)線程
        initBackThread();

    }

    private void initBackThread()
    {
        mCheckMsgThread = new HandlerThread("check-message-coming");
        mCheckMsgThread.start();
        mCheckMsgHandler = new Handler(mCheckMsgThread.getLooper())
        {
            @Override
            public void handleMessage(Message msg)
            {
                checkForUpdate();
                if (isUpdateInfo)
                {
                    mCheckMsgHandler.sendEmptyMessageDelayed(MSG_UPDATE_INFO, 1000);
                }
            }
        };
    }

    /**
     * 模擬從服務(wù)器解析數(shù)據(jù)
     */
    private void checkForUpdate()
    {
        try
        {
            //模擬耗時(shí)
            Thread.sleep(1000);
            mHandler.post(new Runnable()
            {
                @Override
                public void run()
                {
                    String result = "實(shí)時(shí)更新中彤钟,當(dāng)前大盤指數(shù):<font color='red'>%d</font>";
                    result = String.format(result, (int) (Math.random() * 3000 + 1000));
                    mTvServiceInfo.setText(Html.fromHtml(result));
                }
            });

        } catch (InterruptedException e)
        {
            e.printStackTrace();
        }

    }

    @Override
    protected void onDestroy()
    {
        super.onDestroy();
        //釋放資源
        mCheckMsgThread.quit();
    }
}

HandlerThread 的源碼

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

    protected void onLooperPrepared() {
    }

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

Looper.loop()的核心代碼:

while (true) {  
    Message msg = queue.next(); // might block  
    if (msg != null) {  
        if (msg.target == null) {  
            // No target is a magic identifier for the quit message.  
            return;  
        }  

        long wallStart = 0;  
        long threadStart = 0;  

        // This must be in a local variable, in case a UI event sets the logger  
        Printer logging = me.mLogging;  
        if (logging != null) {  
            logging.println(">>>>> Dispatching to " + msg.target + " " +  
                    msg.callback + ": " + msg.what);  
            wallStart = SystemClock.currentTimeMicro();  
            threadStart = SystemClock.currentThreadTimeMicro();  
        }  

        msg.target.dispatchMessage(msg); 


public void quit() {  
    Message msg = Message.obtain();  
    // NOTE: By enqueueing directly into the message queue, the  
    // message is left with a null target.  This is how we know it is  
    // a quit message.  
    mQueue.enqueueMessage(msg, 0);  
}  

是一個(gè)無(wú)限循環(huán)来候,退出循環(huán)的條件是:msg.target == null;
也就是說(shuō),如果我們向此looper的MessageQueue發(fā)送一個(gè)target為null的message逸雹,就可以停止這個(gè)線程的遠(yuǎn)行营搅。

停止HandlerThread的方法就是使用quit方法云挟,具體調(diào)用形式如下:
mHandlerThread.getLooper().quit();

(2). 線程池管理

new Thread(new Runnable() {
    @Override
    public void run() {
    // TODO Auto-generated method stub
    }
}).start();
  • 這樣new出來(lái)的匿名對(duì)象會(huì)存在一些問題:
    由于是匿名的,無(wú)法對(duì)它進(jìn)行管理如果需要多次執(zhí)行這個(gè)操作就new多次转质,可能創(chuàng)建多個(gè)园欣,占用系統(tǒng)資源無(wú)法執(zhí)行更多的操作
  • 使用線程池的好處:
    可以重復(fù)利用存在的線程,減少系統(tǒng)的開銷休蟹;利用線程池可以執(zhí)行定時(shí)砂代、并發(fā)數(shù)的控制
  • 線程池的作用:
    線程池作用就是限制系統(tǒng)中執(zhí)行線程的數(shù)量哪工。
    原理:根據(jù)系統(tǒng)的環(huán)境情況字柠,可以自動(dòng)或手動(dòng)設(shè)置線程數(shù)量孽鸡,達(dá)到運(yùn)行的最佳效果;少了浪費(fèi)了系統(tǒng)資源盈魁,多了造成系統(tǒng)擁擠效率不高翔怎。用線程池控制線程數(shù)量,其他線程排隊(duì)等候备埃。一個(gè)任務(wù)執(zhí)行完畢姓惑,再?gòu)年?duì)列的中取最前面的任務(wù)開始執(zhí)行褐奴。若隊(duì)列中沒有等待進(jìn)程按脚,線程池的這一資源處于等待。當(dāng)一個(gè)新任務(wù)需要運(yùn)行時(shí)敦冬,如果線程池中有等待的工作線程辅搬,就可以開始運(yùn)行了;否則進(jìn)入等待隊(duì)列脖旱。
為什么要用線程池:

1.減少了創(chuàng)建和銷毀線程的次數(shù)堪遂,每個(gè)工作線程都可以被重復(fù)利用,可執(zhí)行多個(gè)任務(wù)萌庆。
2.可以根據(jù)系統(tǒng)的承受能力溶褪,調(diào)整線程池中工作線線程的數(shù)目,防止因?yàn)橄倪^多的內(nèi)存践险,而把服務(wù)器癱瘓(每個(gè)線程需要大約
1MB內(nèi)存猿妈,線程開的越多,消耗的內(nèi)存也就越大巍虫,最后死機(jī))彭则。

Java通過Executors提供四種線程池
  • newCachedThreadPool創(chuàng)建一個(gè)可緩存線程池,如果線程池長(zhǎng)度超過處理需要占遥,可靈活回收空閑線程俯抖,若無(wú)可回收,則新建線程瓦胎。
  • newFixedThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池芬萍,可控制線程最大并發(fā)數(shù)尤揣,超出的線程會(huì)在隊(duì)列中等待。
  • newScheduledThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池担忧,支持定時(shí)及周期性任務(wù)執(zhí)行芹缔。
  • newSingleThreadExecutor 創(chuàng)建一個(gè)單線程化的線程池,它只會(huì)用唯一的工作線程來(lái)執(zhí)行任務(wù)瓶盛,保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級(jí))執(zhí)行最欠。

java線程池的使用參考鏈接:
https://www.cnblogs.com/dolphin0520/p/3932921.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市惩猫,隨后出現(xiàn)的幾起案子芝硬,更是在濱河造成了極大的恐慌,老刑警劉巖轧房,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拌阴,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡奶镶,警方通過查閱死者的電腦和手機(jī)迟赃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)厂镇,“玉大人纤壁,你說(shuō)我怎么就攤上這事∞嘈牛” “怎么了酌媒?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)迄靠。 經(jīng)常有香客問我秒咨,道長(zhǎng),這世上最難降的妖魔是什么掌挚? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任雨席,我火速辦了婚禮,結(jié)果婚禮上吠式,老公的妹妹穿的比我還像新娘陡厘。我一直安慰自己,他們只是感情好奇徒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布雏亚。 她就那樣靜靜地躺著,像睡著了一般摩钙。 火紅的嫁衣襯著肌膚如雪罢低。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音网持,去河邊找鬼宜岛。 笑死,一個(gè)胖子當(dāng)著我的面吹牛功舀,可吹牛的內(nèi)容都是我干的萍倡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼辟汰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼列敲!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起帖汞,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤戴而,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后翩蘸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體所意,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年催首,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扶踊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡郎任,死狀恐怖秧耗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涝滴,我是刑警寧澤绣版,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布胶台,位于F島的核電站歼疮,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏诈唬。R本人自食惡果不足惜韩脏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望铸磅。 院中可真熱鬧赡矢,春花似錦、人聲如沸阅仔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)八酒。三九已至空民,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背界轩。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工画饥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人浊猾。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓抖甘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親葫慎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子衔彻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

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