Java并發(fā)編程實(shí)戰(zhàn)基礎(chǔ)構(gòu)建模塊

同步容器類

同步容器類包括Vector和Hashtable以及由Collections.synchronizedXxx等工廠方法創(chuàng)建的同步封裝器類。這些類實(shí)現(xiàn)線程安全的方式是:將它們的狀態(tài)封裝起來伦吠,并對每個(gè)公有方法都進(jìn)行同步妆兑,使得每次只有一個(gè)線程能訪問容器的狀態(tài)。同步容器對所有容器狀態(tài)的訪問都串行化毛仪,嚴(yán)重降低了并發(fā)性搁嗓;當(dāng)多個(gè)線程競爭鎖時(shí),吞吐量嚴(yán)重下降箱靴。

同步容器類存在的問題

同步容器類都是線程安全的腺逛,但是在某些情況下可能需要額外的客戶端加鎖來保護(hù)復(fù)合操作。

比如衡怀,在Vecotr中棍矛,getLast()和deleteLast()操作安疗,如果是在多線程的環(huán)境下運(yùn)行,如果不加鎖茄靠,會產(chǎn)生異常情況茂契。一個(gè)線程在getLast()后,另一個(gè)線程deleteLast()慨绳,然后該線程繼續(xù)執(zhí)行掉冶,進(jìn)行deleteLast()操作,此時(shí)會拋出下標(biāo)越界的異常脐雪。

又比如厌小,在迭代的過程中,使用get(index)的操作战秋,如果有多個(gè)線程運(yùn)行璧亚,可能會刪除其中元素,同樣會造成異常脂信。

對于如上的情況癣蟋,我們需要通過客戶端加鎖來解決線程安全的問題。如在迭代時(shí)加鎖:

synchronized(vector){for(inti=0;i

迭代器

在迭代或者for-each循環(huán)語法時(shí)狰闪,對容器類進(jìn)行迭代的標(biāo)準(zhǔn)方式都是使用Iterator疯搅。然而,在設(shè)計(jì)同步容器類的迭代器時(shí)并沒有考慮到并發(fā)修改的問題埋泵,并且它們表現(xiàn)出的行為時(shí)“及時(shí)失敗”的幔欧,也就是當(dāng)它們發(fā)現(xiàn)容器在迭代過程中被修改時(shí),就會拋出ConcurrentModificationException丽声。

如果在迭代期間礁蔗,對容器加鎖,首先會降低效率雁社,提高線程的等待時(shí)間浴井;然后還可能會產(chǎn)生死鎖;降低了吞吐量和CPU的利用率霉撵。

如果不希望在迭代期間加鎖磺浙,可以使用克隆容器的方法,并在克隆副本上進(jìn)行迭代喊巍。

加鎖可以防止迭代器拋出ConcurrentModificationException,但是要在所有對容器進(jìn)行迭代的地方都要加鎖箍鼓。如hashCode,equals,containsAll,removeAll,retainAll等方法崭参,在以容器為參數(shù)時(shí),都會對容器進(jìn)行迭代款咖。這些間接的迭代操作可能拋出ConcurrentModificationException何暮。

并發(fā)容器

Java 5.0提供了多種并發(fā)容器類來改進(jìn)同步容器的性能奄喂。同步容器對所有容器狀態(tài)的訪問都串行化,嚴(yán)重降低了并發(fā)性海洼;當(dāng)多個(gè)線程競爭鎖時(shí)跨新,吞吐量嚴(yán)重下降。

并發(fā)容器是針對多個(gè)線程并發(fā)訪問設(shè)計(jì)的坏逢。通過并發(fā)容器來替代同步容器域帐,可以極大地提高伸縮性并降低風(fēng)險(xiǎn)。并發(fā)容器包括ConcurrentHashMap(替代Map),CopyOnWriteArrayList(替代List),ConcurrentLinkedQueue,BlockingQueue等等是整。

ConcurrentHashMap

同步容器類在執(zhí)行每個(gè)操作期間都持有一個(gè)鎖肖揣。ConcurrentHashMap采用了不同的加鎖策略來提供更高的并發(fā)性和伸縮性。它并不是將每個(gè)方法都在同一個(gè)鎖上同步浮入,而是使用一種粒度更細(xì)的加鎖機(jī)制來實(shí)現(xiàn)更大程度的共享龙优,這種機(jī)制稱為分段鎖。

分段鎖機(jī)制使得任意數(shù)量的讀取線程可以并發(fā)訪問Map事秀,執(zhí)行讀取操作的線程和執(zhí)行寫入操作的線程可以并發(fā)訪問Map彤断,并且一定數(shù)量的寫入線程可以并發(fā)地修改Map,因此提高了并發(fā)訪問的吞吐量易迹。

并發(fā)容器增強(qiáng)了同步容器類宰衙,它們提供的迭代器不會拋出ConcurrentModificationException,因此不需要在迭代過程中對容器加鎖赴蝇。其迭代器具有弱一致性菩浙,可以容忍并發(fā)的修改,在創(chuàng)建迭代器時(shí)會遍歷已有元素句伶,并可以(但是不保證)在迭代器被構(gòu)造后將修改操作反映給容器劲蜻。size(),isEmpty()等方法返回的是一個(gè)近似值。

由于ConcurrentHashMap與Hashtable和synchronizedMap有更多的優(yōu)勢考余,因此大多數(shù)情況應(yīng)該使用并發(fā)容器類先嬉,至于當(dāng)需要對整個(gè)容器加鎖進(jìn)行獨(dú)占訪問時(shí),才應(yīng)該放棄使用并發(fā)容器楚堤。

注意疫蔓,此時(shí)不能再通過客戶端加鎖新建新的原子操作了,客戶端只能對并發(fā)容器自身加鎖身冬,但并發(fā)容器內(nèi)部使用的并不是自身鎖衅胀。

CopyOnWriteArrayList

寫入時(shí)復(fù)制容器,在每次修改時(shí)都會加鎖并創(chuàng)建和重新發(fā)布一個(gè)新的容器副本酥筝,直接修改容器引用滚躯,從而實(shí)現(xiàn)可見性。

寫操作在一個(gè)復(fù)制的數(shù)組上進(jìn)行,讀操作還是在原始數(shù)組中進(jìn)行掸掏,讀寫分離茁影,互不影響。寫操作需要加鎖丧凤,防止并發(fā)寫入時(shí)導(dǎo)致寫入數(shù)據(jù)丟失募闲。寫操作結(jié)束之后需要把原始數(shù)組指向新的復(fù)制數(shù)組。

CopyOnWriteArrayList 在寫操作的同時(shí)允許讀操作愿待,大大提高了讀操作的性能浩螺,因此很適合讀多寫少的應(yīng)用場景。

但是 CopyOnWriteArrayList 有其缺陷:

內(nèi)存占用:在寫操作時(shí)需要復(fù)制一個(gè)新的數(shù)組呼盆,使得內(nèi)存占用為原來的兩倍左右年扩;

數(shù)據(jù)不一致:讀操作不能讀取實(shí)時(shí)性的數(shù)據(jù),因?yàn)椴糠謱懖僮鞯臄?shù)據(jù)還未同步到讀數(shù)組中访圃。

阻塞隊(duì)列

阻塞隊(duì)列支持生產(chǎn)者-消費(fèi)者模式厨幻。簡化了開發(fā)過程,消除了生產(chǎn)者和消費(fèi)者之間的代碼依賴性腿时。阻塞隊(duì)列簡化了生產(chǎn)者-消費(fèi)者設(shè)計(jì)的實(shí)現(xiàn)過程况脆。一種常見的生產(chǎn)者-消費(fèi)者設(shè)計(jì)模式就是線程池與工作隊(duì)列的組合。

阻塞隊(duì)列提供了四種處理方法:

拋出異常批糟,使用add(e)插入格了,remove()刪除,element()查詢徽鼎。當(dāng)阻塞隊(duì)列滿時(shí)盛末,插入元素;當(dāng)隊(duì)列空否淤,刪除元素都會拋出異常悄但。

返回特殊值,使用offer(e)插入石抡,poll()刪除檐嚣,peek()查詢。插入時(shí)啰扛,如果成功返回true嚎京,移除時(shí),如果沒有對應(yīng)的元素返回null隐解。

阻塞鞍帝,使用put(e)插入,take()刪除煞茫。隊(duì)列滿帕涌,插入元素時(shí)會阻塞岩臣;隊(duì)列空,取元素會阻塞宵膨。

超時(shí)退出:使用offer(e,time,unit)插入,poll(time,unit)刪除炸宵。當(dāng)隊(duì)列滿時(shí)辟躏,會阻塞,超過一定的時(shí)間土全,線程會退出捎琐。

阻塞隊(duì)列有多種實(shí)現(xiàn)。

ArrayBlokcingQueue和LinkedBlockingQueue分別是數(shù)組和鏈表結(jié)構(gòu)組成的有界的FIFO阻塞隊(duì)列裹匙。

PriorityBlockingQueue是一個(gè)支持優(yōu)先級排序的無界阻塞隊(duì)列瑞凑。

SynchronousQueue是一個(gè)不存儲元素的阻塞隊(duì)列,它不會為隊(duì)列中元素維護(hù)存儲空間概页。

LinkedTransferQueue:一個(gè)由鏈表結(jié)構(gòu)組成的無界阻塞隊(duì)列籽御。

LinkedBlockingDeque:一個(gè)由鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列。

雙端隊(duì)列與工作密取

Java 6提供了Dqueue和BlockingDeque惰匙,是雙端隊(duì)列技掏,實(shí)現(xiàn)了在隊(duì)列頭和隊(duì)列尾的高效插入和移除。雙端隊(duì)列適用于工作密取模式项鬼。在工作密取中哑梳,每個(gè)消費(fèi)者都有各自的雙端隊(duì)列。如果一個(gè)消費(fèi)者完成了自己的雙端隊(duì)列的全部工作绘盟,可以從其他消費(fèi)者雙端隊(duì)列末尾秘密的獲取工作鸠真。因?yàn)楣ぷ髡呔€程不會再單個(gè)共享的任務(wù)隊(duì)列上發(fā)生競爭。適用于既是生產(chǎn)者又是消費(fèi)者問題龄毡。

阻塞方法與中斷方法

線程會阻塞或暫停執(zhí)行吠卷。被阻塞的線程必須等待某個(gè)不受它控制的事件發(fā)生后才能繼續(xù)執(zhí)行。當(dāng)在代碼中調(diào)用一個(gè)可以拋出InterruptedException的方法時(shí)稚虎,自己的方法就編程了阻塞方法撤嫩,必須處理中斷的響應(yīng)。如果這個(gè)方法被中斷蠢终,那么它將努力提前結(jié)束狀態(tài)序攘。

處理中斷的響應(yīng)有兩種基本選擇:

傳遞InterruptedException,把該異常拋出給方法的調(diào)用者寻拂。

恢復(fù)中斷程奠,捕獲異常,并調(diào)用當(dāng)前線程的interrupt方法恢復(fù)中斷祭钉,引發(fā)更高層的代碼中斷瞄沙。

publicvoidrun(){try{? ? ? ? something();? ? }catch(InterruptedException e){? ? ? ? Thread.currentThread().interrupt();? ? }}

同步工具類

同步工具類可以是任何一個(gè)對象,只要它根據(jù)其自身的狀態(tài)來協(xié)調(diào)線程的控制流。包括阻塞隊(duì)列距境,信號量申尼,柵欄以及閉鎖。

閉鎖

閉鎖用來確保某些活動直到其他活動都完成了才繼續(xù)執(zhí)行垫桂。如果有多個(gè)線程师幕,其中一個(gè)線程需要等到其他所有線程活動結(jié)束后才繼續(xù)執(zhí)行,使用閉鎖诬滩。

CountDownLatch是一種閉鎖的實(shí)現(xiàn)霹粥,可以使得一個(gè)或者多個(gè)線程等待一組事情發(fā)生。包括一個(gè)計(jì)數(shù)器疼鸟,表示需要等待的事件數(shù)量后控;countDown方法用來遞減計(jì)數(shù)器,表示有一個(gè)事件已經(jīng)發(fā)生了空镜;await方法等待計(jì)數(shù)器為0浩淘,表示所有需要等待的事情已經(jīng)發(fā)生。

// 初始化閉鎖吴攒,并設(shè)置資源個(gè)數(shù)CountDownLatch latch =newCountDownLatch(2);Thread t1 =newThread(newRunnable(){publicvoidrun(){// 加載資源1加載資源的代碼……// 本資源加載完后馋袜,閉鎖-1latch.countDown();? ? }} ).start();Thread t2 =newThread(newRunnable(){publicvoidrun(){// 加載資源2資源加載代碼……// 本資源加載完后,閉鎖-1latch.countDown();? ? }} ).start();Thread t3 =newThread(newRunnable(){publicvoidrun(){// 本線程必須等待所有資源加載完后才能執(zhí)行l(wèi)atch.await();// 當(dāng)閉鎖數(shù)量為0時(shí)舶斧,await返回欣鳖,執(zhí)行接下來的任務(wù)任務(wù)代碼……? ? }} ).start();復(fù)制代碼

柵欄(同步屏障)

閉鎖是一次性對象,一旦進(jìn)入終止?fàn)顟B(tài)茴厉,就不能被重置泽台。柵欄類似于閉鎖,能阻塞一組進(jìn)程直到某個(gè)時(shí)間發(fā)生矾缓。柵欄與閉鎖的區(qū)別在于怀酷,所有線程必須同時(shí)到達(dá)柵欄位置,才能繼續(xù)執(zhí)行嗜闻。

若有多條線程蜕依,他們到達(dá)屏障時(shí)將會被阻塞,只有當(dāng)所有線程都到達(dá)屏障時(shí)才能打開屏障琉雳,所有線程同時(shí)執(zhí)行样眠,若有這樣的需求可以使用同步屏障。此外翠肘,當(dāng)屏障打開的同時(shí)還能指定執(zhí)行的任務(wù)檐束。

閉鎖只會阻塞一條線程,目的是為了讓該條任務(wù)線程滿足條件后執(zhí)行束倍;

而同步屏障會阻塞所有線程被丧,目的是為了讓所有線程同時(shí)執(zhí)行(實(shí)際上并不會同時(shí)執(zhí)行盟戏,而是盡量把線程啟動的時(shí)間間隔降為最少)。

// 創(chuàng)建同步屏障對象甥桂,并制定需要等待的線程個(gè)數(shù) 和 打開屏障時(shí)需要執(zhí)行的任務(wù)CyclicBarrier barrier =newCyclicBarrier(3,newRunnable(){publicvoidrun(){//當(dāng)所有線程準(zhǔn)備完畢后觸發(fā)此任務(wù)}});// 啟動三條線程for(inti=0; i<3; i++ ){newThread(newRunnable(){publicvoidrun(){// 等待柿究,(每執(zhí)行一次barrier.await,同步屏障數(shù)量-1黄选,直到為0時(shí)笛求,打開屏障)barrier.await();// 任務(wù)任務(wù)代碼……? ? ? ? }? ? } ).start();}復(fù)制代碼

信號量

信號量用于控制同時(shí)訪問某個(gè)特定資源的操作數(shù)量,或者執(zhí)行某個(gè)指定操作的數(shù)量糕簿。計(jì)數(shù)信號量還可以用來實(shí)現(xiàn)某種資源池,或者對容器施加邊界狡孔。

信號量可以用于實(shí)現(xiàn)資源池懂诗,也可以用于將容器變?yōu)橛薪缱枞萜鳌P盘柫抗芾碇唤M虛擬的許可苗膝,在執(zhí)行操作時(shí)首先獲取許可殃恒,并在使用以后釋放許可。如果沒有許可辱揭,將阻塞直到有許可或被中斷离唐,超時(shí)。

信號量的使用場景是问窃,有m個(gè)資源亥鬓,n個(gè)線程,且n>m域庇,同一時(shí)刻只能允許m條線程訪問資源嵌戈。

// 創(chuàng)建信號量對象,并給予3個(gè)資源Semaphore semaphore =newSemaphore(3);// 開啟10條線程for(inti=0; i<10; i++ ) {newThread(newRunnbale(){publicvoidrun(){// 獲取資源听皿,若此時(shí)資源被用光熟呛,則阻塞,直到有線程歸還資源semaphore.acquire();// 任務(wù)代碼……// 釋放資源semaphore.release();? ? ? ? }? ? } ).start();}

FutureTask

可以用作閉鎖尉姨,是一種可以生成結(jié)果的Runnable庵朝,可以處于以下三種狀態(tài):等待運(yùn)行,正在運(yùn)行和運(yùn)行完成又厉。當(dāng)FutureTask進(jìn)入完成狀態(tài)后九府,它會停止在這個(gè)狀態(tài)上。

FutureTask在Executor框架中表示異步任務(wù)覆致,此外還可以用來表示一些時(shí)間較長的運(yùn)算昔逗,這些計(jì)算可以在使用計(jì)算結(jié)構(gòu)之前啟動。

實(shí)戰(zhàn):構(gòu)建緩存

首先篷朵,使用HashMap和同步機(jī)制來初始化緩存勾怒。

publicinterfaceComputable{Vcompute(A arg)throwsInterruptedException;}publicclassExpensiveFuncimplementsComputable{@OverridepublicBigIntegercompute(String arg)throwsInterruptedException{returnnewBigInteger(arg);? ? }}publicclassMemoizer1implementsComputable{privatefinalMap cache=newHashMap<>();privatefinalComputable c;publicMemoizer1(Computable<A,V> c){this.c=c;? ? }@OverridepublicsynchronizedVcompute(A arg)throwsInterruptedException{? ? ? ? V result=cache.get(arg);if(result==null){? ? ? ? ? ? result=c.compute(arg);? ? ? ? ? ? cache.put(arg,result);? ? ? ? }returnresult;? ? }}

在這種實(shí)現(xiàn)方法中婆排,使用HashMap保存之前計(jì)算的結(jié)果。首先檢查需要的結(jié)果是否已經(jīng)在緩存中笔链,如果存在則返回之前計(jì)算段只,否則將計(jì)算結(jié)果緩存到HashMap再返回。

為了確保線程安全鉴扫,將整個(gè)compute方法進(jìn)行同步赞枕。但是這樣伸縮性差,緩存的性能并沒有得到提升坪创。

下面使用ConcurrentHashMap替換HashMap炕婶。但是,這種方法存在一些不足莱预,當(dāng)兩個(gè)線程同時(shí)調(diào)用compute時(shí)柠掂,可能會導(dǎo)致計(jì)算得到相同的值。這樣是低效的依沮,因?yàn)榫彺娴淖饔镁褪潜苊庀嗤臄?shù)據(jù)被計(jì)算多次涯贞。其問題在于,如果某個(gè)線程啟動了一個(gè)計(jì)算危喉,而其他線程并不知道這個(gè)計(jì)算正在進(jìn)行宋渔,很可能會重復(fù)這個(gè)計(jì)算。

針對如上問題辜限,我們考慮可以使用FutureTask來解決皇拣。使用該類來表示計(jì)算的過程,如果有結(jié)果可用薄嫡,則返回結(jié)果审磁,否則一直阻塞。

publicclassMemo2implementsComputable{privatefinalMap> cache=newConcurrentHashMap<>();privatefinalComputablec;publicMemo2(Computable<A,V>c){this.c=c;? ? }@OverridepublicVcompute(A arg)throwsInterruptedException{? ? ? ? Future future=cache.get(arg);if(future==null){? ? ? ? ? ? Callable eval=newCallable() {@OverridepublicVcall()throwsException{returnc.compute(arg);? ? ? ? ? ? ? ? }? ? ? ? ? ? };? ? ? ? ? ? FutureTask ft=newFutureTask<>(eval);? ? ? ? ? ? future=cache.putIfAbsent(arg,ft);if(future==null){? ? ? ? ? ? ? ? future=ft;? ? ? ? ? ? ? ? ft.run();? ? ? ? ? ? }? ? ? ? }try{returnfuture.get();? ? ? ? }catch(ExecutionException e){? ? ? ? ? ? e.printStackTrace();? ? ? ? }returnnull;? ? }}

在此我向大家推薦一個(gè)架構(gòu)學(xué)習(xí)交流群岂座。交流學(xué)習(xí)群號:938837867 暗號:555 里面會分享一些資深架構(gòu)師錄制的視頻錄像:有Spring态蒂,MyBatis,Netty源碼分析费什,高并發(fā)钾恢、高性能、分布式鸳址、微服務(wù)架構(gòu)的原理瘩蚪,JVM性能優(yōu)化、分布式架構(gòu)等這些成為架構(gòu)師必備

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末稿黍,一起剝皮案震驚了整個(gè)濱河市疹瘦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌巡球,老刑警劉巖言沐,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邓嘹,死亡現(xiàn)場離奇詭異,居然都是意外死亡险胰,警方通過查閱死者的電腦和手機(jī)汹押,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來起便,“玉大人棚贾,你說我怎么就攤上這事∮茏郏” “怎么了妙痹?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鼻疮。 經(jīng)常有香客問我怯伊,道長,這世上最難降的妖魔是什么陋守? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮利赋,結(jié)果婚禮上水评,老公的妹妹穿的比我還像新娘。我一直安慰自己媚送,他們只是感情好中燥,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著塘偎,像睡著了一般疗涉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吟秩,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天咱扣,我揣著相機(jī)與錄音,去河邊找鬼涵防。 笑死闹伪,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的壮池。 我是一名探鬼主播偏瓤,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼椰憋!你這毒婦竟也來了厅克?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤橙依,失蹤者是張志新(化名)和其女友劉穎证舟,沒想到半個(gè)月后硕旗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡褪储,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年卵渴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鲤竹。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡浪读,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辛藻,到底是詐尸還是另有隱情碘橘,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布吱肌,位于F島的核電站痘拆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏氮墨。R本人自食惡果不足惜纺蛆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望规揪。 院中可真熱鬧桥氏,春花似錦、人聲如沸猛铅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奸忽。三九已至堕伪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間栗菜,已是汗流浹背欠雌。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留疙筹,地道東北人桨昙。 一個(gè)月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像腌歉,于是被迫代替她去往敵國和親蛙酪。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

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