ThreadLocal源碼解析边败,內(nèi)存泄露以及傳遞性

我想ThreadLocal這東西袱衷,大家或多或少都了解過一點(diǎn),我在接觸ThreadLocal的時候笑窜,覺得這東西很神奇致燥,在網(wǎng)上看了很多博客,也看了一些書排截,總覺得有一個坎跨不過去嫌蚤,所以對ThreadLocal一直是一知半解的辐益,好在這東西在實(shí)際開發(fā)中畢竟用的不多,所以也就得過且過了搬葬。當(dāng)然我說的“用的不多”荷腊,只是對于普通的上層業(yè)務(wù)開發(fā)而言,其實(shí)在很多框架中急凰,都用到了ThreadLocal女仰,甚至有的還對ThreadLocal做了進(jìn)一步的改進(jìn)。但是ThreadLocal也算是并發(fā)編程的基礎(chǔ)抡锈,所以還真的有必要疾忍,也必須要好好研究下的。今天我們就來好好看看ThreadLocal床三。

ThreadLocal簡單應(yīng)用

我們知道在多線程下一罩,操作一個共享變量,很容易會發(fā)生矛盾撇簿,要解決這問題聂渊,最好的辦法當(dāng)然是每個線程都擁有自己的變量,其他的線程無法訪問四瘫,所謂“沒有共享汉嗽,就沒有傷害”。那么如何做到呢找蜜?ThreadLocal就這樣華麗麗的登場了饼暑。

我們先來看看簡單的應(yīng)用:

    public static void main(String[] args) {
        ThreadLocal threadLocal = new ThreadLocal();
        threadLocal.set("Hello");
        System.out.println("當(dāng)前線程是:" + Thread.currentThread().getName());
        System.out.println("在當(dāng)前線程中獲取:" + threadLocal.get());
        new Thread(() -> System.out.println("現(xiàn)在線程是"+Thread.currentThread().getName()+"嘗試獲认醋觥:" + threadLocal.get())).start();
    }

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

當(dāng)前線程是:main
在當(dāng)前線程中獲裙选:Hello
現(xiàn)在線程是Thread-0嘗試獲取:null

運(yùn)行結(jié)果很好理解诚纸,在主線程中往threadLocal 塞了一個值撰筷,只有在同一個線程下,才可以獲得值畦徘,在其他線程就無法獲取值了毕籽。

嘗試自己寫一個ThreadLocal

在我們探究ThreadLocal之前,先讓我們思考一個問題旧烧,如果叫你來實(shí)現(xiàn)ThreadLocal影钉,你會怎么做画髓?
ThreadLocal的目標(biāo)就在于讓每個線程都有只屬于自己的變量掘剪。最直接的辦法就是新建一個泛型類,在類中定義一個map奈虾,key是Long類型的夺谁,用來保存線程的id廉赔,value是T類型的,用來保存具體的數(shù)據(jù)匾鸥。

  • set的時候蜡塌,就獲取當(dāng)前線程的id,把這個作為key勿负,往map里面塞數(shù)據(jù)馏艾;

  • get的時候,還是獲取當(dāng)前線程的id奴愉,把這個作為key琅摩,然后從map中取出數(shù)據(jù)。

就像下面這個樣子:

public class ThreadLocalTest {
    public static void main(String[] args) {
        CodeBearThreadLocal threadLocal = new CodeBearThreadLocal();
        threadLocal.set("Hello");

        System.out.println("當(dāng)前線程是:" + Thread.currentThread().getName());
        System.out.println("在當(dāng)前線程中獲榷稹:" + threadLocal.get());
        new Thread(() -> System.out.println("現(xiàn)在線程是" + Thread.currentThread().getName() + "嘗試獲确孔省:" + threadLocal.get())).start();
    }
}

class CodeBearThreadLocal<T> {
    private ConcurrentHashMap<Long , T> hashMap = new ConcurrentHashMap<>();

    void set(T value) {
        hashMap.put(Thread.currentThread().getId(),value);
    }

    T get() {
       return hashMap.get(Thread.currentThread().getId());
    }
}

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

當(dāng)前線程是:main
在當(dāng)前線程中獲取:Hello
現(xiàn)在線程是Thread-0嘗試獲忍赐贰:null

可以看到運(yùn)行結(jié)果和“正版的ThreadLocal”是一模一樣的轰异。

探究ThreadLocal

我們自己也寫了一個ThreadLocal,看上去一點(diǎn)問題也沒有暑始,僅僅幾行代碼就把功能實(shí)現(xiàn)了搭独,給自己鼓個掌。那正版的ThreadLocal是怎么實(shí)現(xiàn)的呢蒋荚?核心應(yīng)該和我們寫的差不多吧戳稽。遺憾的是,正版的ThreadLocal和我們寫的可以說完全不一樣期升。

我們現(xiàn)在看看正版的ThreadLocal是怎么做的惊奇。

set

    public void set(T value) {
        Thread t = Thread.currentThread();//獲取當(dāng)前的線程
        ThreadLocalMap map = getMap(t);//獲取ThreadLocalMap 
        if (map != null)//如果map不為null,調(diào)用set方法塞入值
            map.set(this, value);
        else
            createMap(t, value);//新建map
    }
  1. 獲取當(dāng)前的線程賦值給t播赁;
  2. 調(diào)用getMap方法颂郎,傳入t,也就是傳入當(dāng)前線程容为,獲取ThreadLocalMap乓序,賦值給map;
  3. 如果map不為null坎背,調(diào)用set方法塞入值替劈;
  4. 如果map為null,則調(diào)用createMap方法得滤。

讓我們來看看getMap方法:

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

getMap方法比較簡單陨献,直接返回了傳進(jìn)來的線程對象的threadLocals,說明threadLocals定義在Thread類里面懂更,是ThreadLocalMap 類型的眨业,讓我們看看threadLocals的定義:

public class Thread implements Runnable{
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

看到這個定義急膀,大家一定有點(diǎn)暈,我們是跟著ThreadLocal的set方法進(jìn)來的龄捡,怎么到了這里又回到ThreadLocal了卓嫂,大家別著急,我們再來看看ThreadLocalMap是什么鬼聘殖?


image.png

ThreadLocalMap是ThreadLocal的靜態(tài)內(nèi)部類晨雳,我們的數(shù)據(jù)就是保存在ThreadLocalMap里面,更詳細(xì)的說我們的數(shù)據(jù)就保存在ThreadLocal類中的ThreadLocalMap靜態(tài)內(nèi)部類中的Entry[]里面奸腺。

讓我們把關(guān)系理一理悍募,確實(shí)有點(diǎn)混亂,Thread類里面定義了ThreadLocal.ThreadLocalMap字段洋机,ThreadLocalMap是TheadLocal的內(nèi)部靜態(tài)類坠宴,其中的Entry[]是用來保存數(shù)據(jù)的。這就意味著绷旗,每一個Thread實(shí)例中的ThreadLocalMap都是獨(dú)一無二的喜鼓,又不相互干擾。等等衔肢,這不就揭開了ThreadLocal的神秘面紗了嗎庄岖?原來ThreadLocal是這么做到讓每個線程都有自己的變量的。

如果你還不清楚的話角骤,沒關(guān)系隅忿,我們再來說的詳細(xì)點(diǎn)。在我們實(shí)現(xiàn)的ThreadLocal中邦尊,是利用map實(shí)現(xiàn)數(shù)據(jù)存儲的背桐,key就是線程Id,你可以理解為key就是Thread的實(shí)例蝉揍,value就是我們需要保存的數(shù)據(jù)链峭,當(dāng)我們調(diào)用get方法的時候,就是利用線程Id又沾,你可以理解為利用Thread的實(shí)例去map中取出數(shù)據(jù)弊仪,這樣我們?nèi)〕龅臄?shù)據(jù)就肯定是這個線程持有的。比如這個線程是A杖刷,你傳入了B線程的線程Id励饵,也就是傳入了B線程的Thread的實(shí)例就肯定無法取出線程A所持有的數(shù)據(jù),這點(diǎn)應(yīng)該毫無疑問把滑燃。但是役听,在正版的ThreadLocal中,數(shù)據(jù)是直接存在Thread實(shí)例中的,這樣每個線程的數(shù)據(jù)就被天然的隔離了禾嫉。

現(xiàn)在我們解決了一個問題,ThreadLocal是如何實(shí)現(xiàn)線程數(shù)據(jù)隔離的蚊丐,但是還有一個問題熙参,也就是我初學(xué)ThreadLocal看了很多博客,仍然百思不得其解的問題麦备,既然數(shù)據(jù)是保存在ThreadLocalMap中的Entry[]的孽椰,那么就代表可以保存多個數(shù)據(jù),不然用一個普通的成員變量不就OK了嗎凛篙,為什么要用數(shù)組呢黍匾?但是ThreadLocal提供的set方法沒有重載啊,如果先set一個“hello”呛梆,又set一個“bye”锐涯,那么“bye”肯定會把“hello”給覆蓋掉啊,又不像HashMap一樣填物,有key和value的概念纹腌。這個問題真的困擾我很久,后面終于知道了原因了滞磺,我們可以new多個ThreadLocal呀升薯,就像這樣:

    public static void main(String[] args) {
        ThreadLocal threadLocal1 = new ThreadLocal();
        threadLocal1.set("Hello");

        ThreadLocal threadLocal2 = new ThreadLocal();
        threadLocal2.set("Bye");
    }

這樣一來,會發(fā)生什么情況呢击困?再次放出set的代碼涎劈,以免大家要往上翻很久:

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

threadLocal1,threadLocal2都調(diào)用了set方法阅茶,盡管threadLocal1和threadLocal2是不同的實(shí)例蛛枚,但是它們在同一個線程啊,所以getMap獲取的ThreadLocalMap是同一個脸哀,這樣就變成了在同一個ThreadLocalMap保存了多個數(shù)據(jù)坤候。

具體是怎么保存數(shù)據(jù)的,這個代碼就比較復(fù)雜了企蹭,包括的細(xì)節(jié)太多了白筹,我看的也不是很懂,只知道一個大概谅摄,我們先來看看Entry的定義把:

    static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
}

Entry又是ThreadLocalMap的靜態(tài)內(nèi)部類徒河,里面只有一個字段value,也就是說和HashMap是不同的送漠,沒有鏈表的概念顽照。

        private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
  1. 把table賦值給局部變量tab,這個table就是保存數(shù)據(jù)的字段,類型是Entry[]代兵;
  2. 獲取tab的長度賦值給len尼酿;
  3. 求出下標(biāo)i;
  4. 一個for循環(huán)植影,先根據(jù)第三步求出的下標(biāo)裳擎,從tab里獲取指定下標(biāo)的值e,如果e==null思币,就不會進(jìn)入這個for循環(huán)鹿响,也就是如果當(dāng)前的位置是空的,就直接進(jìn)入第五步谷饿;如果當(dāng)前的位置已經(jīng)有數(shù)據(jù)了惶我,判斷這個位置的ThreadLocal和我們即將要插入進(jìn)去的是不是同一個,如果是的話博投,用新值替換掉绸贡;如果不是的話,則尋找下一個空位毅哗;
  5. 把創(chuàng)建出來的Entry實(shí)例放入tab恃轩,這里

其中的細(xì)節(jié)有點(diǎn)多,看的有點(diǎn)迷糊黎做,但是最關(guān)鍵的應(yīng)該還算是看懂了叉跛。

get

   public T get() {
        Thread t = Thread.currentThread();//獲取當(dāng)前線程
        ThreadLocalMap map = getMap(t);//傳入當(dāng)前線程,獲取當(dāng)前線程的ThreadLocalMap 
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//傳入ThreadLocal實(shí)例蒸殿,獲取Entry
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;//返回值
            }
        }
        return setInitialValue();
    }
  1. 獲取當(dāng)前線程筷厘;
  2. 獲取當(dāng)前線程的ThreadLocalMap;
  3. 傳入ThreadLocal實(shí)例宏所,獲取Enrty酥艳;
  4. 返回值。
        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }
  1. 求出下標(biāo)i爬骤;
  2. 根據(jù)下標(biāo)i充石,從table中取出值,賦值給e霞玄;
  3. 如果e不為空骤铃,并且e持有的ThreadLocal實(shí)例和傳進(jìn)去的ThreadLocal實(shí)例是同一個,直接返回坷剧;
  4. 如果e為空惰爬,或者e持有的ThreadLocal實(shí)例和傳進(jìn)去的ThreadLocal實(shí)例不是同一個,則繼續(xù)往下找惫企。

小總結(jié)

set方法和get方法都分析完畢了撕瞧,我們來做一個小總結(jié)浅侨。我們在外面所使用的ThreadLocal更像是一個工具類挪圾,本身不保存任何數(shù)據(jù)撒轮,而真正的數(shù)據(jù)是保存在Thread實(shí)例中的汹来,這樣就天然的完成了線程數(shù)據(jù)的隔離。最后送上一張圖页畦,來幫助大家更好的理解ThreadLocal:

image.png

內(nèi)存泄露

我們再來看看Entry的定義:

    static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

Entry繼承了WeakReference胖替,關(guān)于WeakReference是什么東西,不是本文的重點(diǎn)寇漫,大家可以自行查閱。WeakReference包裹了ThreadLocal殉摔,我們再來看Entry的構(gòu)造方法州胳,調(diào)用了super(k),傳入了我們傳進(jìn)來的ThreadLocal實(shí)例逸月,也就是ThreadLocal被保存到了WeakReference對象中栓撞。這就導(dǎo)致了一個問題,當(dāng)ThreadLocal沒有強(qiáng)依賴碗硬,ThreadLocal會在下一次發(fā)生GC時被回收瓤湘,key是被回收了,但是value卻沒有被回收呀恩尾,所以就出現(xiàn)了Entry[]存在key為NULL弛说,但是value不為NULL的項(xiàng)的情況,要想回收的話翰意,可以讓創(chuàng)建ThreadLocal的線程的生命周期結(jié)束木人。但是在實(shí)際的開發(fā)中,線程有極大可能是和程序同生共死的冀偶,只要程序不停止醒第,線程就一直在蹦跶。所以我們在使用完ThreadLocal方法后进鸠,最好要手動調(diào)用remove方法稠曼,就像這樣:

    public static void main(String[] args) {
        ThreadLocal<String> threadLocal = new ThreadLocal();
        try {
            threadLocal.set("Hello");
            threadLocal.get();
        } finally {
            threadLocal.remove();
        }
    }

別忘了,最好把remove方法放在finally中哦客年。

InheritableThreadLocal

我們還是來看博客一開頭的例子:

  public static void main(String[] args) {
        ThreadLocal threadLocal = new ThreadLocal();
        threadLocal.set("Hello");
        System.out.println("當(dāng)前線程是:" + Thread.currentThread().getName());
        System.out.println("在當(dāng)前線程中獲认挤:" + threadLocal.get());
        new Thread(() -> System.out.println("現(xiàn)在線程是" + Thread.currentThread().getName() + "嘗試獲取:" + threadLocal.get())).start();
    }

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

當(dāng)前線程是:main
在當(dāng)前線程中獲攘抗稀:Hello
現(xiàn)在線程是Thread-0嘗試獲然柔:null

代碼后面new出來Thread是由主線程創(chuàng)建的,所以可以說這個線程是主線程的子線程榔至,在主線程往ThreadLocal set的值抵赢,在子線程中獲取不到,這很好理解,因?yàn)樗麄儾⒉皇峭粋€線程铅鲤,但是我希望子線程能繼承主線程的ThreadLocal中的數(shù)據(jù)划提。InheritableThreadLocal出現(xiàn)了,完全可以滿足這樣的需求:

    public static void main(String[] args) {
        ThreadLocal threadLocal = new InheritableThreadLocal();
        threadLocal.set("Hello");
        System.out.println("當(dāng)前線程是:" + Thread.currentThread().getName());
        System.out.println("在當(dāng)前線程中獲刃舷怼:" + threadLocal.get());
        new Thread(() -> System.out.println("現(xiàn)在線程是" + Thread.currentThread().getName() + "嘗試獲扰敉:" + threadLocal.get())).start();
    }

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

當(dāng)前線程是:main
在當(dāng)前線程中獲取:Hello
現(xiàn)在線程是Thread-0嘗試獲群痢:Hello

這樣就讓子線程繼承了主線程的ThreadLocal的數(shù)據(jù)伊履,說的更準(zhǔn)確些,是子線程繼承了父線程的ThreadLocal的數(shù)據(jù)款违。

那到底是如何做到的呢唐瀑?還是看代碼把。

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

InheritableThreadLocal繼承了ThreadLocal插爹,并且重寫了三個方法哄辣,當(dāng)我們首次調(diào)用InheritableThreadLocal的set的時候,會調(diào)用InheritableThreadLocal的createMap方法赠尾,這就創(chuàng)建了ThreadLocalMap的實(shí)例力穗,并且賦值給inheritableThreadLocals,這個inheritableThreadLocals定義在哪里呢气嫁?和ThreadLocal的threadLocals一樣当窗,也是定義在Thread類中。當(dāng)我們再次調(diào)用set方法的時候寸宵,會調(diào)用InheritableThreadLocal的getMap方法超全,返回的也是inheritableThreadLocals,也就是把原先的threadLocals給替換掉了邓馒。

當(dāng)我們創(chuàng)建一個線程嘶朱,會調(diào)用Thread的構(gòu)造方法:

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

init方法比較長,我只復(fù)制出和我們要探究的問題相關(guān)的代碼:

 Thread parent = currentThread();
 if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
  1. 獲取當(dāng)前線程光酣,此時當(dāng)前線程是父線程疏遏。
  2. 如果父線程的inheritableThreadLocals不為空,就跑到if中去救军。當(dāng)然這里肯定是不為空的财异,我們上面已經(jīng)說了,調(diào)用InheritableThreadLocal中的set方法唱遭,直接操作的是inheritableThreadLocals戳寸,if中做了什么,就是傳入了父線程的inheritableThreadLocals拷泽,創(chuàng)建了新的ThreadLocalMap疫鹊,賦值給Thead實(shí)例的inheritableThreadLocals袖瞻,這樣子線程就擁有了父線程的ThreadLocalMap,也就完成了ThreadLocal的繼承與傳遞拆吆。

這篇博客到這里就結(jié)束了聋迎,東西還是挺多的,但是都是挺重要的枣耀,特別是ThreadLocal的原因和產(chǎn)生內(nèi)存泄露的原因和避免的方法霉晕。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市捞奕,隨后出現(xiàn)的幾起案子牺堰,更是在濱河造成了極大的恐慌,老刑警劉巖颅围,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伟葫,死亡現(xiàn)場離奇詭異,居然都是意外死亡谷浅,警方通過查閱死者的電腦和手機(jī)扒俯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門奶卓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來一疯,“玉大人,你說我怎么就攤上這事夺姑《昭” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵盏浙,是天一觀的道長眉睹。 經(jīng)常有香客問我,道長废膘,這世上最難降的妖魔是什么竹海? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮丐黄,結(jié)果婚禮上斋配,老公的妹妹穿的比我還像新娘。我一直安慰自己灌闺,他們只是感情好艰争,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著桂对,像睡著了一般甩卓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蕉斜,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天逾柿,我揣著相機(jī)與錄音缀棍,去河邊找鬼。 笑死鹿寻,一個胖子當(dāng)著我的面吹牛睦柴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播毡熏,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼坦敌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了痢法?” 一聲冷哼從身側(cè)響起狱窘,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎财搁,沒想到半個月后蘸炸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡尖奔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年搭儒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片提茁。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡淹禾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出茴扁,到底是詐尸還是另有隱情铃岔,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布峭火,位于F島的核電站毁习,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏卖丸。R本人自食惡果不足惜纺且,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望稍浆。 院中可真熱鬧载碌,春花似錦、人聲如沸粹湃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽为鳄。三九已至裳仆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間孤钦,已是汗流浹背歧斟。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工纯丸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人静袖。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓觉鼻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親队橙。 傳聞我的和親對象是個殘疾皇子坠陈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

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