Java并發(fā)編程之基礎(chǔ)知識和多線程簡介

處理器:即中央處理器(CPU,Central Processing Unit),它是一塊超大規(guī)模的集成電路,是一臺計算機(jī)的運算核心(Core)和控制核心( Control Unit)。它的功能主要是解釋計算機(jī)指令以及處理計算機(jī)軟件中的數(shù)據(jù)。

進(jìn)程:進(jìn)程(Process)是計算機(jī)中的程序關(guān)于某數(shù)據(jù)集合上的一次運行活動,是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位,是操作系統(tǒng)結(jié)構(gòu)的基礎(chǔ)。在早期面向進(jìn)程設(shè)計的計算機(jī)結(jié)構(gòu)中,進(jìn)程是程序的基本執(zhí)行實體;在當(dāng)代面向線程設(shè)計的計算機(jī)結(jié)構(gòu)中,進(jìn)程是線程的容器。程序是指令、數(shù)據(jù)及其組織形式的描述,進(jìn)程是程序的實體。

線程:線程有時被稱為輕量級進(jìn)程(Lightweight Process,LWP),是程序執(zhí)行流的最小單元。線程是進(jìn)程中的一個實體,是被系統(tǒng)獨立調(diào)度和分派的基本單位,線程自己不擁有系統(tǒng)資源,只擁有一點兒在運行中必不可少的資源,但它可與同屬一個進(jìn)程的其它線程共享進(jìn)程所擁有的全部資源。

多核處理器和多處理器的運行效率

多核處理器和多處理器不是一個意思,它們的運行效率是不相同的。多核處理器是指一個CPU有多個核心處理器,處理器之間通過CPU內(nèi)部總線進(jìn)行通訊。而多處理器是指簡單的多個CPU工作在同一個系統(tǒng)上,多個CPU之間的通訊是通過主板上的總線進(jìn)行的。因此多核CPU要比多個CPU在一起的工作效率更高(單核性能一致的情況下)。

單核CPU的進(jìn)程并發(fā)執(zhí)行

單核CPU也能執(zhí)行多個進(jìn)程,但是它并不是真正意義上的同時執(zhí)行(并發(fā)執(zhí)行)。雖然在單核CPU的電腦也能開啟多個進(jìn)程,但這些進(jìn)程并不能同時被開啟和執(zhí)行,而是以輪換的機(jī)制執(zhí)行的(是有時間順序的),而CPU處理某個單一的操作速度是極快的,并且極快的在進(jìn)程中切換,從而讓人感覺是同時運行了多個進(jìn)程、同時處理多個操作。

進(jìn)程切換的目的

進(jìn)程切換的原因之一是防止前一個任務(wù)耗時太長,導(dǎo)致后面簡單的任務(wù)等待太久,而且電腦中有許多系統(tǒng)進(jìn)程必須同時處于開啟狀態(tài),所以CPU也必須采取這種辦法來處理。理論上講真正意義上的同時執(zhí)行的進(jìn)程數(shù)不能超過CPU核心數(shù)。

單核CPU的線程并發(fā)執(zhí)行

單核CPU運行多個線程是可行的。因為線程是進(jìn)程中的一個實體,一個進(jìn)程可以開啟多個線程,所以即使只開啟單個進(jìn)程也能開啟多個線程在單核CPU上運行。但是同上理,單核CPU并不能真正意義上的實現(xiàn)線程并發(fā)。

多線程可以實現(xiàn)并行處理,避免了某項任務(wù)長時間占用CPU時間。對于單核單處理器(CPU)來說,為了運行所有這些線程,操作系統(tǒng)為每個獨立線程安排一些CPU時間,操作系統(tǒng)以輪換方式向線程提供時間片,這給人一種假象好像這些線程都在同時運行。多線程實現(xiàn)并發(fā)確實能夠提升性能,但是使用多線程并發(fā)不是必須的。如果兩個非常活躍的線程執(zhí)行很簡單的操作,為了搶奪對CPU的控制權(quán),在線程切換時會消耗很多的CPU資源,反而會降低系統(tǒng)的性能。 最開始線程只是用于分配單個處理器的處理時間的一種工具。但假如操作系統(tǒng)本身支持多個處理器,那么每個線程都可分配給一個不同的處理器,真正進(jìn)入“并行運算”狀態(tài)。從程序設(shè)計語言的角度看,多線程操作最有價值的特性之一就是程序員不必關(guān)心到底使用了多少個處理器,程序員只需將程序編寫成多線程模式即可。程序在邏輯意義上被分割為數(shù)個線程;假如機(jī)器本身安裝了多個處理器,那么程序會運行得更快,毋需作出任何特殊的調(diào)校。

資源共享問題

使用多線程也會和多進(jìn)程一樣,會存在資源共享問題。如果有多個線程同時運行,而且它們試圖訪問相同的資源(共享的資源),這時就會出現(xiàn)問題。而一種可行的辦法就是在使用期間必須進(jìn)入鎖定狀態(tài)。所以一個線程可將資源鎖定(例如,程序設(shè)計中的線程鎖),在完成了它的任務(wù)后,再解開(釋放)這個鎖,使其他線程可以接著使用同樣的資源。

多線程是為了同步完成多項任務(wù),不是為了提高運行效率,而是為了提高資源使用效率來提高系統(tǒng)的效率。線程是在同一時間需要完成多項任務(wù)的時候?qū)崿F(xiàn)的。

處理器結(jié)構(gòu)對并發(fā)程序的影響

對稱多處理器是最主要的多核處理器架構(gòu)。在這種架構(gòu)中所有的CPU共享一條系統(tǒng)總線(BUS)來連接主存。而每一個核又有自己的一級緩存,相對于BUS對稱分布,如下圖:

這種架構(gòu)在并發(fā)程序設(shè)計中,大致會引來兩個問題,一個是內(nèi)存可見性,一個是Cache一致性流量。內(nèi)存可見性屬于并發(fā)安全的問題,Cache一致性流量引起的是性能上的問題。

內(nèi)存可見性:內(nèi)存可見性在單處理器或單線程情況下是不會發(fā)生的。在一個單線程環(huán)境中,一個變量選寫入值,然后在沒有干涉的情況下讀取這個變量,得到的值應(yīng)該是修改過的值。但是在讀和寫不在同一個線程中的時候,情況卻是不可以預(yù)料的。Core1和Core2可能會同時把主存中某個位置的值Load到自己的一級緩存中,而Core1修改了自己一級緩存中的值后,卻不更新主存中的值,這樣對于Core2來講,永遠(yuǎn)看不到Core1對值的修改。在Java程序設(shè)計中,用鎖,關(guān)鍵字volatile,CAS原子操作可以保證內(nèi)存可見。

Cache一致性問題:指的是在SMP結(jié)構(gòu)中,Core1和Core2同時下載了主存中的值到自己的一級緩存中,Core1修改了值后,會通過總線讓Core2中的值失效,Core2發(fā)現(xiàn)自己存的值失效后,會再通過總線從主存中得到新值。總線的通信能力是固定的,通過總線使各CPU的一級緩存值數(shù)據(jù)同步的流量過大,那么總線就會成瓶頸。這種影響屬于性能上的影響,減小同步競爭就能減少一致性流量。

另外通常說的四核八線程實際上是模擬八核。相當(dāng)于在每個邏輯處理器上可以開兩個線程。它的性能在一般情況下比四核四線程快不少,有時也能接近八核的性能。但如果真正遇到高并發(fā)(CPU使用率很高甚至100%)的時候性能是沒法和八核比的。因為四核八線程并不能真正意義上的實現(xiàn)八線程的并發(fā)。

上下文切換

對于單核CPU來說(對于多核CPU),CPU在某個時刻只能運行一個線程,當(dāng)在運行一個線程的過程中轉(zhuǎn)去運行另外一個線程,這個叫做線程上下文切換(對于進(jìn)程也是類似)。由于可能當(dāng)前線程的任務(wù)并沒有執(zhí)行完畢,所以在切換時需要保存線程的運行狀態(tài),以便下次重新切換回來時能夠繼續(xù)切換之前的狀態(tài)運行。線程上下文切換過程中一般會記錄程序計數(shù)器、CPU寄存器狀態(tài)等數(shù)據(jù)。對于線程的上下文切換實際上就是存儲和恢復(fù)CPU狀態(tài)的過程,它使得線程執(zhí)行能夠從中斷點恢復(fù)執(zhí)行。雖然多線程可以使得任務(wù)執(zhí)行的效率得到提升,但是由于在線程切換時同樣會帶來一定的開銷代價,并且多個線程會導(dǎo)致系統(tǒng)資源占用的增加,所以在進(jìn)行多線程編程時要注意這些因素。

同步與異步

同步和異步通常是用來描述一次方法的調(diào)用,同步方法一旦調(diào)用開始,調(diào)用者必須等待方法調(diào)用返回后才能繼續(xù)后續(xù)的操作。異步方法調(diào)用更像一個方法傳遞,一旦調(diào)用開始就會立即返回結(jié)果,調(diào)用者就可以繼續(xù)執(zhí)行后續(xù)的操作。異步方法通常會在另外一個線程中“真實”的執(zhí)行,真?zhèn)€過程不會阻礙調(diào)用者的工作。如果異步調(diào)用需要返回結(jié)果,會在異步調(diào)用真實完成時通知調(diào)用者。

阻塞與非阻塞

阻塞和非阻塞通常用來形容多線程之間的相互影響,比如說臨界區(qū)的資源爭奪。當(dāng)其中某個線程占用了臨界區(qū)的資源,其他所有需要這個資源的線程必須在這個臨界區(qū)中進(jìn)行等待,等待會導(dǎo)致線程掛起,這就是阻塞。非阻塞強(qiáng)調(diào)的是沒有一個線程可以妨礙其它線程的執(zhí)行,所有的線程都可以嘗試不斷前向執(zhí)行。

1.進(jìn)程和線程的概述

每個進(jìn)程都是獨立(self contained)的運行環(huán)境,它可以被看作是一個程序或者是一個應(yīng)用,Java的運行環(huán)境就是一個包含了不同的類和程序的單一進(jìn)程。而線程是在進(jìn)程中執(zhí)行的一個任務(wù),線程可以被稱為輕量級進(jìn)程。線程需要較少的資源來創(chuàng)建和駐留在進(jìn)程中,并且可以共享進(jìn)程中的資源。線程是進(jìn)程的子集,一個進(jìn)程可以有很多線程,每條線程并行執(zhí)行不同的任務(wù)。不同的進(jìn)程使用不同的內(nèi)存空間,而所有的線程共享一片相同的內(nèi)存空間。每個線程都擁有單獨的棧內(nèi)存用來存儲本地數(shù)據(jù)。在多線程的應(yīng)用中,多個線程可以被并發(fā)的執(zhí)行以提高程序的效率,CPU不會因為某個線程需要等待資源而進(jìn)入空閑狀態(tài)。多個線程共享堆內(nèi)存(heap memory),因此創(chuàng)建多個線程去執(zhí)行一些任務(wù)會比創(chuàng)建多個進(jìn)程更好。線程是操作系統(tǒng)能夠進(jìn)行運算調(diào)度的最小單位,它被包含在進(jìn)程之中,是進(jìn)程中的實際運作單位。程序員可以通過它進(jìn)行多處理器編程,你可以使用多線程對運算密集型任務(wù)提速。

2.線程調(diào)度器(Thread Scheduler)和時間分片(Time Slicing)與上下文切換(context-switching)

線程調(diào)度器是一個操作系統(tǒng)服務(wù),它負(fù)責(zé)為Runnable狀態(tài)的線程分配CPU時間片。一旦我們創(chuàng)建一個線程并啟動它,它的執(zhí)行便依賴于線程調(diào)度器的實現(xiàn)。時間分片是指將可用的CPU時間分配給可用的Runnable線程的過程。分配CPU時間可以基于線程優(yōu)先級或者線程等待的時間。不要讓程序依賴于線程的優(yōu)先級,因為線程調(diào)度并不受到Java虛擬機(jī)控制,所以由應(yīng)用程序來控制它是更好的選擇。多線程的上下文切換是指存儲和恢復(fù)CPU狀態(tài)的過程,它使得線程執(zhí)行能夠從中斷點恢復(fù)執(zhí)行。上下文切換是多任務(wù)操作系統(tǒng)和多線程環(huán)境的基本特征。

3.線程的創(chuàng)建與生命周期描述

在Java程序中新建一個線程時線程的狀態(tài)是New;當(dāng)調(diào)用線程的start方法時線程的狀態(tài)是Runnable;線程調(diào)度器為Runnable線程池中的線程分配CPU時間片后,線程的狀態(tài)是Running;當(dāng)線程進(jìn)入同步方法或者同步代碼塊后線程的狀態(tài)是Blocked;其他的線程狀態(tài)還有Waiting,Timed_Waiting和Terminated。有兩種創(chuàng)建線程的方法:一是實現(xiàn)Runnable接口,然后將它傳遞給Thread的構(gòu)造函數(shù),創(chuàng)建一個Thread對象;二是直接繼承Thread類。此處注意:線程類本身就是調(diào)用的Runnable接口。Java不支持類的多重繼承但允許調(diào)用多個接口,如果要繼承其他類,調(diào)用Runnable接口來實現(xiàn)多線程是最好的選擇,,更符合編程的設(shè)計原則。

4.線程的分類與優(yōu)先級描述

線程可以分為用戶線程和守護(hù)線程。在Java程序中創(chuàng)建的線程被稱為用戶線程,守護(hù)線程是在后臺執(zhí)行并且不會阻止JVM終止的線程。當(dāng)沒有用戶線程在運行的時候,JVM關(guān)閉程序并且退出。守護(hù)線程創(chuàng)建的子線程依然是守護(hù)線程。使用Thread類的setDaemon(true)方法可以將線程設(shè)置為守護(hù)線程,但是需要在調(diào)用start方法前調(diào)用這個方法,否則會拋出IllegalThreadStateException異常。每個線程都是具有優(yōu)先級的,具備高優(yōu)先級的線程更容易獲得CPU的時間片從而得到執(zhí)行,但是這并不能保證高優(yōu)先級的線程會在低優(yōu)先級的線程前執(zhí)行,因為這依賴于線程調(diào)度的實現(xiàn)并且這個實現(xiàn)是和操作系統(tǒng)相關(guān)的(OS dependent)。線程優(yōu)先級是個從1到10的整型變量,1代表最低優(yōu)先級而10代表最高優(yōu)先級。

用戶線程和守護(hù)線程的區(qū)別:
1、主線程結(jié)束后用戶線程還會繼續(xù)運行,JVM存活;主線程結(jié)束后守護(hù)線程和JVM的狀態(tài)由下面第2條確定;
2、如果沒有用戶線程,都是守護(hù)線程,那么JVM結(jié)束(隨之而來的是所有的一切煙消云散,包括所有的守護(hù)線程)。

5.線程的運行,暫停與停止描述

Thread.start方法被用來啟動新創(chuàng)建的線程,而且start內(nèi)部調(diào)用了run方法,這和直接調(diào)用run方法的效果是不相同的。Thread.start 方法(native)啟動線程,使之進(jìn)入就緒狀態(tài),當(dāng)cpu分配時間該線程時,由JVM調(diào)度執(zhí)行run 方法。當(dāng)你調(diào)用run方法的時候只會是在原來的線程中調(diào)用并沒有新的線程啟動,start方法才會啟動新線程。Java應(yīng)用中需要run &start 這兩個方法是因為JVM創(chuàng)建一個單獨的線程不同于普通方法的調(diào)用,所以這項工作由線程的start方法來完成,start由本地方法實現(xiàn),需要顯示地被調(diào)用,使用這倆個方法的另外一個好處是任何一個對象都可以作為線程運行,只要實現(xiàn)了Runnable接口,這就避免因繼承了Thread類而造成的Java的多繼承問題。如何強(qiáng)制啟動一個線程:這個問題如同如何強(qiáng)制進(jìn)行Java垃圾回收,雖然可以使用System.gc來進(jìn)行垃圾回收,但是不保證能成功。在Java里面沒有辦法強(qiáng)制啟動一個線程,因為它是被線程調(diào)度器控制著且Java沒有公布相關(guān)的API。

Thread類的Sleep方法可以讓線程暫停一段時間但是并不會讓線程終止,一旦從休眠中喚醒線程,線程的狀態(tài)將會改變?yōu)镽unnable,并且根據(jù)線程調(diào)度從而得到執(zhí)行。JDK提供的stop,suspend 和resume控制方法可以停止線程但是由于潛在的死鎖威脅被棄用了。當(dāng)使用run或者call方法執(zhí)行完的時候線程會自動結(jié)束,如果要手動結(jié)束一個線程可以用volatile布爾變量來退出run方法的循環(huán)或者是取消任務(wù)來中斷線程。

使用Thread類的join方法可以確保所有創(chuàng)建的線程在main方法退出前結(jié)束。Thread類的sleep和yield方法會讓出CPU的控制權(quán),它們一般運行在當(dāng)前正在執(zhí)行的線程上,處于等待狀態(tài)的線程上調(diào)用這些方法是沒有意義的,這也是Thread類的sleep和yield是靜態(tài)方法的原因。Yield方法可以暫停當(dāng)前正在執(zhí)行的線程對象,讓其它有相同優(yōu)先級的線程執(zhí)行。它是一個靜態(tài)方法而且只保證當(dāng)前線程放棄CPU占用而不能保證使其它線程一定能占用CPU,執(zhí)行yield的線程有可能在進(jìn)入到暫停狀態(tài)后馬上又被執(zhí)行。

6.wait和notify的概述

線程之間的通信是通過共享對象或者是使用像阻塞隊列這樣并發(fā)的數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)數(shù)據(jù)共享。可以使用wait和notify方法來實現(xiàn)生產(chǎn)者與消費者模型(Semaphore或者BlockingQueue也可以實現(xiàn))。

Java的每個對象中都有一個鎖(monitor監(jiān)視器) ,wait和notify等方法用于等待對象的鎖或者通知其他線程對象的監(jiān)視器可用。在Java的線程中并沒有可供任何對象使用的鎖和同步器,wait和notify等方法定義在Object中可以確保Java的每一個類都有用于線程間通信的基本方法。Java提供的鎖是對象級的而不是線程級的,每個對象都有鎖且都是通過線程獲得。如果線程需要等待某些鎖那么調(diào)用對象中的wait方法就有意義了,如果wait方法定義在Thread類中,線程正在等待的是哪個鎖就不明顯了。簡單的說就是由于wait,notify和notifyAll都是鎖級別的操作,所以把他們定義在Object類中是因為鎖屬于對象。

實際應(yīng)用中notify方法不能喚醒某個具體的線程,所以它適合于只有一個線程在等待的時候。而notifyAll喚醒所有線程并允許他們爭奪鎖,這樣確保了至少有一個線程能繼續(xù)運行。當(dāng)一個線程需要調(diào)用對象的wait方法的時候,這個線程必須擁有該對象的鎖,接著它就會釋放這個對象鎖并進(jìn)入等待狀態(tài)直到其他線程調(diào)用這個對象上的notify方法。同樣的當(dāng)一個線程需要調(diào)用對象的notify方法時,它會釋放這個對象的鎖,以便其他在等待的線程就可以得到這個對象鎖。由于所有的這些方法都需要線程持有對象的鎖,這樣就只能通過同步來實現(xiàn),所以它們只能在同步方法或者同步塊中被調(diào)用。API強(qiáng)制要求wait和notify方法要在同步塊中調(diào)用,要不然會拋出IllegalMonitorStateException異常,該異常是RuntimeExcpetion的子類,不一定要捕獲且此類異常不會在wait ,notify 和notifyAll 的方法簽名提及,這樣也是為了避免wait和notify之間產(chǎn)生競態(tài)條件。Java程序中wait和sleep都會造成某種形式的暫停,它們可以滿足不同的需要。wait方法用于線程間通信,如果等待條件為真且其它線程被喚醒時它會釋放鎖,而sleep方法僅僅釋放CPU資源或者讓當(dāng)前線程停止執(zhí)行一段時間但不會釋放鎖。

Thread.sleep使當(dāng)前線程在指定的時間處于“非運行”(Not Runnable)狀態(tài)。線程一直持有對象的監(jiān)視器。比如一個線程當(dāng)前在一個同步塊或同步方法中,其它線程不能進(jìn)入該塊或方法中。如果另一線程調(diào)用了 interrupt 方法,它將喚醒那個“睡眠的”線程。注意:sleep 是一個靜態(tài)方法。這意味著只對當(dāng)前線程有效,一個常見的錯誤是調(diào)用t.sleep ,(這里的t是一個不同于當(dāng)前線程的線程)。即便是執(zhí)行t.sleep ,也是當(dāng)前線程進(jìn)入睡眠,而不是t線程。t.suspend 是過時的方法,使用 suspend 導(dǎo)致線程進(jìn)入停滯狀態(tài),該線程會一直持有對象的監(jiān)視器,suspend 容易引起死鎖問題。object.wait 使當(dāng)前線程出于“不可運行”狀態(tài),和 sleep 不同的是wait是object的方法而不是thread。調(diào)用object.wait 時,線程先要獲取這個對象的對象鎖,當(dāng)前線程必須在鎖對象保持同步,把當(dāng)前線程添加到等待隊列中,隨后另一線程可以同步同一個對象鎖來調(diào)用object.notify ,這樣將喚醒原來等待中的線程,然后釋放該鎖。基本上wait /notify 與sleep /interrupt 類似,只是前者需要獲取對象鎖。

7.多線程的同步問題

在多線程程序下,同步能控制對共享資源的訪問。如果沒有同步,當(dāng)一個 Java 線程在修改一個共享變量時,另外一個線程正在使用或者更新同一個變量,這樣容易導(dǎo)致程序出現(xiàn)錯誤的結(jié)果。如果你的代碼所在的進(jìn)程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結(jié)果和單線程運行的結(jié)果是一樣的,而且其他的變量的值也和預(yù)期的是一樣的,就是線程安全的。一個線程安全的計數(shù)器類的同一個實例對象在被多個線程使用的情況下也不會出現(xiàn)計算失誤。JDK中提供了很多這樣的例子,比如可以將集合類分成兩組,線程安全和非線程安全的,Vector是用同步方法來實現(xiàn)線程安全的, 而和它相似的ArrayList不是線程安全的。在Java應(yīng)用中可以使用原子類(atomic concurrent classes),實現(xiàn)并發(fā)鎖,使用volatile關(guān)鍵字,使用不變類和線程安全類來確保線程同步。同步靜態(tài)方法時會獲取該類的“Class”對象,所以當(dāng)一個線程進(jìn)入同步的靜態(tài)方法中時,線程監(jiān)視器獲取類本身的對象鎖,其它線程不能進(jìn)入這個類的任何靜態(tài)同步方法。它不像實例方法,因為多個線程可以同時訪問不同實例同步實例方法。同步塊的鎖的粒度更細(xì),因為它不會鎖住整個對象(實際上也可以鎖住整個對象)。同步方法會鎖住整個對象,哪怕這個類中有多個不相關(guān)聯(lián)的同步塊,這通常會導(dǎo)致他們停止執(zhí)行并需要等待獲得這個對象上的鎖,故實現(xiàn)線程安全時使用同步塊比同步方法好。檢查當(dāng)前線程是否擁有鎖:在java.lang.Thread中有一個方法叫holdsLock,返回true意味著當(dāng)前線程擁有某個具體對象的鎖。

8.volatile關(guān)鍵字在Java中的作用

Java中堆和棧的區(qū)別:棧是一塊和線程緊密相關(guān)的內(nèi)存區(qū)域。每個線程都有自己的棧內(nèi)存,用于存儲本地變量,方法參數(shù)和棧調(diào)用,一個線程中存儲的變量對其它線程是不可見的。而堆是所有線程共享的一片公用內(nèi)存區(qū)域。對象都在堆里創(chuàng)建,為了提升效率線程會從堆中弄一個緩存到自己的棧,如果多個線程使用該變量就可能引發(fā)問題,這時volatile變量就可以發(fā)揮作用了,它要求線程從主存中讀取變量的值。在JVM中-Xss參數(shù)用來控制線程的堆棧大小。

當(dāng)使用volatile關(guān)鍵字去修飾變量的時候,所有線程都會直接讀取該變量并且不緩存它。這就確保了線程讀取到的變量同內(nèi)存中的是一致的。volatile是一個特殊的修飾符,只有成員變量才能使用它,volatile變量規(guī)則:volatile變量可以保證下一個讀取操作會在前一個寫操作之后發(fā)生。volatile變量和atomic變量看起來很像,但功能卻不一樣。Volatile變量可以確保先行關(guān)系,即寫操作會發(fā)生在后續(xù)的讀操作之前,但它并不能保證原子性。例如用volatile修飾count變量那么count++ 操作就不是原子性的。而AtomicInteger類提供的atomic方法可以讓這種操作具有原子性,如getAndIncrement方法會原子性的進(jìn)行增量操作把當(dāng)前值加一,其它數(shù)據(jù)類型和引用變量也可以進(jìn)行相似操作。

9.ThreadLocal概述

ThreadLocal是Java中的特殊變量。每個線程都有一個ThreadLocal,也就是每個線程都擁有了自己獨立的一個變量,這樣可以讓競爭條件被徹底消除。它是為創(chuàng)建代價高昂的對象獲取線程安全的好方法,比如你可以用ThreadLocal讓SimpleDateFormat變成線程安全的,因為那個類創(chuàng)建代價高昂且每次調(diào)用都需要創(chuàng)建不同的實例所以不值得在局部范圍使用它,如果為每個線程提供一個自己獨有的變量拷貝,將大大提高效率。首先通過復(fù)用減少了代價高昂的對象的創(chuàng)建個數(shù)。其次在沒有使用高代價的同步或者不變性的情況下獲得了線程安全。線程局部變量的另一個不錯的例子是ThreadLocalRandom類,它在多線程環(huán)境中減少了創(chuàng)建代價高昂的Random對象的個數(shù)。ThreadLocal用于創(chuàng)建線程的本地變量是因為一個對象的所有線程會共享它的全局變量,所以這些變量不是線程安全的。在應(yīng)用中可以使用同步技術(shù)來確保線程的安全,也可以選擇ThreadLocal變量來確保線程的安全。每個線程都擁有自己的ThreadLocal變量,它們可以使用get和set方法去獲取他們的默認(rèn)值或者在線程內(nèi)部改變他們的值。ThreadLocal 實例通常作為靜態(tài)的私有的(private static)字段出現(xiàn)在一個類中,這個類用來關(guān)聯(lián)一個線程。常見的使用可在DAO模式中見到,當(dāng)DAO類作為一個單例類時,數(shù)據(jù)庫鏈接(connection)被每一個線程獨立的維護(hù)且互不影響(基于線程的單例)。

10.Java中interrupted和isInterrupted方法的區(qū)別

Java中的interrupted 和 isInterrupted的主要區(qū)別是前者會將中斷狀態(tài)清除而后者不會。Java多線程的中斷機(jī)制是用內(nèi)部標(biāo)識來實現(xiàn)的,調(diào)用Thread.interrupt來中斷一個線程就會設(shè)置中斷標(biāo)識為true。當(dāng)中斷線程調(diào)用靜態(tài)方法Thread.interrupted來檢查中斷狀態(tài)時,中斷狀態(tài)會被清零。而非靜態(tài)方法isInterrupted用來查詢其它線程的中斷狀態(tài)且不會改變中斷狀態(tài)標(biāo)識。簡單的說就是任何拋出InterruptedException異常的方法都會將中斷狀態(tài)清零。無論如何一個線程的中斷狀態(tài)有有可能被其它線程調(diào)用中斷來改變。

11.循環(huán)中檢查等待條件的好處

處于等待狀態(tài)的線程可能會收到錯誤警報和偽喚醒,如果不在循環(huán)中檢查等待條件,程序就會在沒有滿足結(jié)束條件的情況下退出。因此當(dāng)一個等待線程醒來時,不能認(rèn)為它原來的等待狀態(tài)仍然是有效的,在notify方法調(diào)用之后和等待線程醒來之前這段時間它可能會改變,這就是在循環(huán)中使用wait方法效果更好的原因。此處注意與多線程忙循環(huán)的區(qū)別:忙循環(huán)就是程序員用循環(huán)讓一個線程等待,不像傳統(tǒng)方法wait, sleep或yield它們都放棄了CPU控制,而忙循環(huán)不會放棄CPU,它就是在運行一個空循環(huán)。這么做的目的是為了保留CPU緩存,在多核系統(tǒng)中,一個等待線程醒來的時候可能會在另一個內(nèi)核運行,這樣會重建緩存。為了避免重建緩存和減少等待重建的時間就可以使用它了。

12.Java中同步集合與并發(fā)集合的區(qū)別

同步集合與并發(fā)集合都為多線程和并發(fā)提供了合適的線程安全的集合,不過并發(fā)集合的可擴(kuò)展性更高。Java集合類都是快速失敗的,這就意味著當(dāng)集合被改變且一個線程在使用迭代器遍歷集合的時候,迭代器的next方法將拋出ConcurrentModificationException異常。并發(fā)容器支持并發(fā)的遍歷和并發(fā)的更新。主要的類有ConcurrentHashMap,CopyOnWriteArrayList 和CopyOnWriteArraySet。ConcurrentHashMap把實際map劃分成若干部分來實現(xiàn)它的可擴(kuò)展性和線程安全。這種劃分是使用并發(fā)度獲得的,它是ConcurrentHashMap類構(gòu)造函數(shù)的一個可選參數(shù)且默認(rèn)值為16,這樣在多線程情況下就能避免爭用。ConcurrentHashMap是弱一致性的。

13.Thread Group概述

ThreadGroup是一個類,它的目的是提供關(guān)于線程組的信息。ThreadGroup API沒有比Thread提供更多的功能。它有兩個主要的功能:一是獲取線程組中處于活躍狀態(tài)線程的列表;二是設(shè)置為線程設(shè)置未捕獲異常處理器(uncaught exception handler)。但Thread類添加了setUncaughtExceptionHandler 方法,所以ThreadGroup不建議繼續(xù)使用。

14.多線程的死鎖(Deadlock),活鎖和線程轉(zhuǎn)儲(Thread Dump)

Java多線程中的死鎖是指兩個或兩個以上的進(jìn)程在執(zhí)行過程中,因爭奪資源而造成的一種互相等待的現(xiàn)象,若無外力作用它們都將無法推進(jìn)下去,從而讓你的程序掛起無法完成任務(wù)。這種情況產(chǎn)生至少需要兩個以上的線程和兩個以上的資源。

死鎖的發(fā)生必須滿足以下四個條件:

a.互斥條件:一個資源每次只能被一個進(jìn)程使用。

b.請求與保持條件:一個進(jìn)程因請求資源而阻塞時,對已獲得的資源保持不放。

c.不剝奪條件:進(jìn)程已獲得的資源,在末使用完之前,不能強(qiáng)行剝奪。

d.循環(huán)等待條件:若干進(jìn)程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系。

線程死鎖發(fā)生的兩種情況:

a.當(dāng)兩個線程相互調(diào)用 Thread.join

b.當(dāng)兩個線程使用嵌套的同步塊,一個線程占用了另外一個線程必需的鎖,互相等待時被阻塞就有可能出現(xiàn)死鎖。

避免死鎖最簡單的方法就是阻止循環(huán)等待條件,將系統(tǒng)中所有的資源設(shè)置標(biāo)志位、排序,規(guī)定所有的進(jìn)程申請資源必須以一定的順序(升序或降序)做操作來避免死鎖。分析死鎖需要查看Java應(yīng)用程序的線程轉(zhuǎn)儲,需要找出那些狀態(tài)為BLOCKED的線程和他們等待的資源。每個資源都有唯一的id,用這個id我們可以找出哪些線程已經(jīng)擁有了它的對象鎖。避免嵌套鎖,在需要的地方使用鎖和避免無限期等待是避免死鎖的通常辦法。線程轉(zhuǎn)儲是一個JVM活動線程的列表,它用來分析系統(tǒng)瓶頸和死鎖。獲取線程轉(zhuǎn)儲:可以使用Profiler,Kill -3命令,jstack(對線程id進(jìn)行操作,可通過jps獲取到)工具等。線程轉(zhuǎn)儲一般用來獲取Java中的線程堆棧,當(dāng)你獲取線程堆棧時,JVM會把所有線程的狀態(tài)存到日志文件或者輸出到控制臺。

活鎖和死鎖類似,不同之處在于處于活鎖的線程或進(jìn)程的狀態(tài)是不斷改變的,活鎖可以認(rèn)為是一種特殊的饑餓。活鎖和死鎖的主要區(qū)別是前者進(jìn)程的狀態(tài)可以改變但是卻不能繼續(xù)執(zhí)行。當(dāng)所有線程阻塞,或者由于需要的資源無效而不能處理,不存在非阻塞線程使資源可用。Java API中線程活鎖可能發(fā)生在以下情形:當(dāng)所有線程在程序中執(zhí)行Object.wait (0),參數(shù)為0的wait方法,程序?qū)l(fā)生活鎖直到在相應(yīng)的對象上有線程調(diào)用Object.notify 或者Object.notifyAll ;當(dāng)所有線程卡在無限循環(huán)中。

15.使用Java Timer類創(chuàng)建有特定時間間隔的任務(wù)

java.util.Timer是一個工具類,用于安排一個線程在未來的某個特定時間執(zhí)行。Timer類可以用來安排一次性任務(wù)或者周期任務(wù)。java.util.TimerTask是一個實現(xiàn)了Runnable接口的抽象類,實際運用中需要去繼承這個類來創(chuàng)建定時任務(wù)并使用Timer去安排它的執(zhí)行。

16.Java線程池概述

創(chuàng)建線程要花費昂貴的資源和時間,如果任務(wù)來了才創(chuàng)建線程那么響應(yīng)時間會變長,而且一個進(jìn)程能創(chuàng)建的線程數(shù)有限。為了避免這些問題,在程序啟動的時候就創(chuàng)建若干線程來響應(yīng)處理,它們被稱為線程池,里面的線程叫工作線程。Java API提供了Executor框架讓你可以創(chuàng)建不同的線程池。比如單線程池每次處理一個任務(wù);數(shù)目固定的線程池或者是緩存線程池(適合很多生存期短的任務(wù)的程序的可擴(kuò)展線程池)。線程池管理了一組工作線程和一個用于放置等待執(zhí)行的任務(wù)的隊列。java.util.concurrent.Executors提供了一個 java.util.concurrent.Executor接口的實現(xiàn)用于創(chuàng)建線程池,ScheduledThreadPoolExecutor用于創(chuàng)建周期任務(wù)。

Java線程池中submit和execute方法的區(qū)別:兩個方法都可以向線程池提交任務(wù),execute方法的返回類型是void,它定義在Executor接口中, 而submit方法可以返回持有計算結(jié)果的Future對象,它定義在ExecutorService接口中,它擴(kuò)展了Executor接口,其它線程池類像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有這些方法。

在提交任務(wù)的時候,如果線程池隊列已滿,該任務(wù)會阻塞直到線程池隊列有空位,但是如果一個任務(wù)不能被調(diào)度執(zhí)行那么ThreadPoolExecutor的submit方法將會拋出RejectedExecutionException異常。

17.原子操作,原子類(atomic classes)和Immutable對象

原子操作是指一個不受其他操作影響的操作任務(wù)單元。原子操作是在多線程環(huán)境下避免數(shù)據(jù)不一致必須的手段。

int++并不是一個原子操作,當(dāng)一個線程讀取它的值并加1時,另外一個線程有可能會讀到之前的值,這就會引發(fā)錯誤,可以使用同步技術(shù)來保證原子性。java.util.concurrent.atomic包提供了int和long類型的裝箱類,它們可以自動的保證對于他們的操作是原子性的并且不需要使用同步。此處注意與Immutable對象的區(qū)別:不變性有助于簡化已經(jīng)很復(fù)雜的并發(fā)程序。Immutable對象可以在沒有同步的情況下共享,降低了對該對象進(jìn)行并發(fā)訪問時的同步化開銷。可是Java沒有@Immutable這個注解符,要創(chuàng)建不可變類,要實現(xiàn)下面幾個步驟:通過構(gòu)造方法初始化所有成員、對變量不要提供setter方法、將所有的成員聲明為私有的,這樣就不允許直接訪問這些成員、在getter方法中,不要直接返回對象本身,而是克隆對象,并返回對象的拷貝。

18.Java Concurrency API中的Lock接口(Lock interface)對比同步的優(yōu)勢

Lock接口比同步方法和同步塊提供了更具擴(kuò)展性的鎖操作。它們允許更靈活的結(jié)構(gòu),可以具有完全不同的性質(zhì),并且可以支持多個相關(guān)類的條件對象。

Lock的優(yōu)勢:可以使鎖更公平;可以使線程在等待鎖的時候響應(yīng)中斷;可以讓線程嘗試獲取鎖,并在無法獲取鎖的時候立即返回或者等待一段時間;可以在不同的范圍,以不同的順序獲取和釋放鎖。lock接口在多線程和并發(fā)編程中最大的優(yōu)勢是它們?yōu)樽x和寫分別提供了鎖,它能滿足你寫像ConcurrentHashMap這樣的高性能數(shù)據(jù)結(jié)構(gòu)和有條件的阻塞。

19.Executors框架概述

Executor框架是一個根據(jù)一組執(zhí)行策略調(diào)用,調(diào)度,執(zhí)行和控制的異步任務(wù)的框架。無限制的創(chuàng)建線程會引起應(yīng)用程序內(nèi)存溢出,創(chuàng)建線程池可以限制線程的數(shù)量并且可以回收再利用這些線程,利用Executors框架可以創(chuàng)建一個線程池。Executors為Executor,ExecutorService,ScheduledExecutorService,ThreadFactory和Callable類提供了一些工具方法,Executors可以用于方便的創(chuàng)建線程池。

20.使用阻塞隊列來實現(xiàn)生產(chǎn)者-消費者模型

java.util.concurrent.BlockingQueue的特性是:當(dāng)隊列是空的時,從隊列中獲取或刪除元素的操作將會被阻塞,或者當(dāng)隊列是滿時,往隊列里添加元素的操作會被阻塞。阻塞隊列不接受空值,當(dāng)你嘗試向隊列中添加空值的時候,它會拋出NullPointerException。阻塞隊列的實現(xiàn)都是線程安全的,所有的查詢方法都是原子的并且使用了內(nèi)部鎖或者其他形式的并發(fā)控制。BlockingQueue接口是java collections框架的一部分,它主要用于實現(xiàn)生產(chǎn)者-消費者問題。此處注意同阻塞式方法的區(qū)別,阻塞式方法是指程序會一直等待該方法完成,期間不做其他事情,ServerSocket的accept方法就是一直等待客戶端連接。這里的阻塞是指調(diào)用結(jié)果返回之前,當(dāng)前線程會被掛起,直到得到結(jié)果之后才會返回。此外還有異步和非阻塞式方法在任務(wù)完成前就返回。如果線程遇到了IO阻塞時無法中止線程,如果線程因為調(diào)用wait、sleep、或者join方法而導(dǎo)致的阻塞,可以中斷線程并且通過拋出InterruptedException來喚醒它。

21.Callable,Runnable和Future概述

Runnable和Callable都代表那些要在不同的線程中執(zhí)行的任務(wù),它們的主要區(qū)別是Callable的call方法可以返回值和拋出異常,而Runnable的run方法沒有這些功能。Callable可以返回裝載有計算結(jié)果的Future對象。Callable接口使用泛型去定義它的返回類型。Executors類提供了方法在線程池中執(zhí)行Callable內(nèi)的任務(wù)。由于Callable任務(wù)是并行的故需要等待它返回的結(jié)果,故在線程池提交Callable任務(wù)后返回一個Future對象,從而得知Callable任務(wù)的狀態(tài)和得到Callable返回的執(zhí)行結(jié)果。Future提供了get方法用來等待Callable結(jié)束并獲取它的執(zhí)行結(jié)果。在Java并發(fā)程序中FutureTask表示一個可以取消的異步運算。它有啟動和取消運算、可以查詢是否完成和取回運算結(jié)果等方法。只有當(dāng)運算完成的時候結(jié)果才能取回,如果運算尚未完成get方法將會阻塞。FutureTask對象可以對調(diào)用了Callable和Runnable的對象進(jìn)行包裝,由于FutureTask也是調(diào)用了Runnable接口所以它可以提交給Executor來執(zhí)行。FutureTask是Future的一個基礎(chǔ)實現(xiàn),可以將它同Executors使用處理異步任務(wù)。通常不需要使用FutureTask類,但打算重寫Future接口的一些方法并保持原來基礎(chǔ)的實現(xiàn)時會變得非常有用。

22.應(yīng)該遵循的多線程最佳實踐

給線程起個有意義的名字以方便查找bug或追蹤。避免鎖定和縮小同步的范圍,鎖花費的代價高昂且上下文切換更耗費時間空間,試試最低限度的使用同步和鎖,縮小臨界區(qū)。多用同步類少用wait和notify,首先CountDownLatch, Semaphore, CyclicBarrier和Exchanger這些同步類簡化了編碼操作,而用wait和notify很難實現(xiàn)對復(fù)雜控制流的控制。其次這些類是由最好的企業(yè)編寫和維護(hù),在后續(xù)的JDK中它們還會不斷優(yōu)化和完善,使用這些更高等級的同步工具可以讓程序很容易就獲得優(yōu)化。多用并發(fā)集合少用同步集合,并發(fā)集合比同步集合的可擴(kuò)展性更好,所以在并發(fā)編程時使用并發(fā)集合效果更好。

23.Java中的Fork Join框架

fork join框架是JDK7中出現(xiàn)的一款高效的工具,Java開發(fā)人員可以通過它充分利用現(xiàn)代服務(wù)器上的多處理器。它是專門為了那些可以遞歸劃分成許多子模塊設(shè)計的,目的是將所有可用的處理能力用來提升程序的性能。fork join框架一個巨大的優(yōu)勢是它使用了工作竊取算法,可以完成更多任務(wù)的工作線程可以從其它線程中竊取任務(wù)來執(zhí)行。

24. Java中synchronized和ReentrantLock的區(qū)別

Java中通過synchronized關(guān)鍵字來實現(xiàn)互斥,但是它不能擴(kuò)展鎖之外的方法或者塊邊界,嘗試獲取鎖時不能中途取消等。Java中通過Lock接口提供了更復(fù)雜的控制來解決這些問題,ReentrantLock類實現(xiàn)了Lock,它擁有與 synchronized相同的并發(fā)性和內(nèi)存語義且它還具有可擴(kuò)展性。一般而言,讀寫鎖是用來提升并發(fā)程序性能的鎖分離技術(shù)的成果。Java中的ReadWriteLock是Java 5中新增的一個接口,一個ReadWriteLock維護(hù)一對關(guān)聯(lián)的鎖,一個用于只讀操作一個用于寫。在沒有寫線程的情況下一個讀鎖可能會同時被多個讀線程持有。寫鎖是獨占的,你可以使用JDK中的ReentrantReadWriteLock來實現(xiàn)這個規(guī)則,它最多支持65535個寫鎖和65535個讀鎖。

25.Java中CyclicBarrier和CountDownLatch的區(qū)別

CyclicBarrier和CountDownLatch都可以用來讓一組線程等待其它線程。與CyclicBarrier不同的是CountdownLatch 不能重新使用。

26.Java的內(nèi)存模型

Java內(nèi)存模型規(guī)定和指引Java程序在不同的內(nèi)存架構(gòu)、CPU和操作系統(tǒng)間有確定性地行為。它在多線程的情況下尤其重要。Java內(nèi)存模型對一個線程所做的變動能被其它線程可見提供了保證,它們之間是先行發(fā)生關(guān)系。這個關(guān)系定義了一些規(guī)則讓程序員在并發(fā)編程時思路更清晰。比如先行發(fā)生關(guān)系確保了線程內(nèi)的代碼能夠按先后順序執(zhí)行,這被稱為程序次序規(guī)則。對于同一個鎖,一個解鎖操作一定要發(fā)生在時間上后發(fā)生的另一個鎖定操作之前,也叫做管程鎖定規(guī)則。前一個對volatile的寫操作在后一個volatile的讀操作之前,也叫volatile變量規(guī)則。一個線程內(nèi)的任何操作必需在這個線程的start調(diào)用之后,也叫作線程啟動規(guī)則。一個線程的所有操作都會在線程終止之前,線程終止規(guī)則。一個對象的終結(jié)操作必需在這個對象構(gòu)造完成之后,也叫對象終結(jié)規(guī)則。

27.Java中的競態(tài)條件

競態(tài)條件會導(dǎo)致程序在并發(fā)情況下出現(xiàn)一些bugs。多線程對一些資源的競爭的時候就會產(chǎn)生競態(tài)條件,如果首先要執(zhí)行的程序競爭失敗排到后面執(zhí)行了,那么整個程序就會出現(xiàn)一些不確定的bugs。因為線程間的隨機(jī)競爭從而導(dǎo)致這種bugs很難發(fā)現(xiàn)而且會重復(fù)出現(xiàn)。最常見的例子就是無序處理。

28.線程在運行時異常的處理

如果異常沒有被捕獲該線程將會停止執(zhí)行。Thread.UncaughtExceptionHandler是用于處理未捕獲異常造成線程突然中斷情況的一個內(nèi)嵌接口。當(dāng)一個未捕獲異常將造成線程中斷的時候JVM會使用Thread.getUncaughtExceptionHandler來查詢線程的UncaughtExceptionHandler并將線程和異常作為參數(shù)傳遞給handler的uncaughtException方法進(jìn)行處理。此處注意:無論同步塊是正常還是異常退出,里面的線程都會釋放鎖,但是鎖接口要手動在finally block里釋放鎖實現(xiàn)。

29.Java中Semaphore概述

Java中的Semaphore是一種新的同步類,它是一個計數(shù)信號。從概念上講,信號量維護(hù)了一個許可集合。如有必要,在許可可用前會阻塞每一個acquire,然后再獲取該許可。每個release添加一個許可,從而可能釋放一個正在阻塞的獲取者。但是不使用實際的許可對象,Semaphore只對可用許可的號碼進(jìn)行計數(shù),并采取相應(yīng)的行動。信號量常常用于多線程的代碼中,比如數(shù)據(jù)庫連接池。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,003評論 2 374

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