讀《java并發(fā)編程》零星筆記

java并發(fā)編程
書中代碼

線程安全

“當(dāng)多個線程訪問某個類時,不管運(yùn)行時環(huán)境采用何種調(diào)度方式,或者這些線程將如何交替執(zhí)行,并且在主調(diào)代碼中不需要任何額外的同步或協(xié)同,這個類都能表現(xiàn)出正確的行為,那么就稱這個類是線程安全的”。

  • 對象的狀態(tài)是指存儲在狀態(tài)變量(實(shí)例或者靜態(tài)域)中的數(shù)據(jù)。

  • “共享”意味著變量可以有多個線程同時訪問,而“可變”意味著變量的值在其生命周期內(nèi)可以變化。

  • 編寫線程安全的代碼的核心在于:要對狀態(tài)訪問操作進(jìn)行管理,特別時對共享的(shared)和可變(Mutable)的狀態(tài)的訪問。

  • 一個對象是否需要是線程安全的,取決于它是否需要被多個線程訪問,不需要被多線程訪問,則不談它的安全性。要使對象是線程安全的,需要采用同步機(jī)制來協(xié)同對象可變狀態(tài)的訪問,如果無法實(shí)現(xiàn)協(xié)同,則可能導(dǎo)致數(shù)據(jù)被破壞以及錯誤結(jié)果。

  • 無狀態(tài)對象一定時線程安全的,如Servlet,就沒有定義任何的屬性。所以多個線程調(diào)用它的方法總能得到正確的結(jié)果。但是自定義的Servlet子類中如果定義了一些狀態(tài),那么共享對象需要同步機(jī)制來保證對象可變狀態(tài)的訪問。

內(nèi)存可見性

  • 做到內(nèi)存可見性的原則:對一個變量執(zhí)行unlock操作之前,必須先把此變量同步回主內(nèi)存中(執(zhí)行stroe、write操作)
    必看-內(nèi)存可見性視頻
  • 多個線程對共享變量進(jìn)行讀寫操作時,如果線程A修改了共享變量,其它線程應(yīng)該能夠知道這個修改。實(shí)現(xiàn)方法有:synchronized、final、Volatile變量。
  • 枷鎖來實(shí)現(xiàn)內(nèi)存可見,內(nèi)置鎖可以用于確保某個線程以一種可預(yù)測的方式來查看另一個線程的執(zhí)行結(jié)果。

深入理解Java虛擬機(jī)筆記---原子性、可見性、有序性

重排序

  • JVM為了能夠充分利用多核處理器的強(qiáng)大性能,在缺乏同步的情況下,Java內(nèi)存模型允許編譯器對操作順序進(jìn)行重排序,并將數(shù)值寄存在寄存器中。此外,他還允許CPU對操作順序進(jìn)行重排序,并將計(jì)算值緩存在處理器特定的緩存中。(如果沒有重排序存在,在編寫并發(fā)代碼時可以省去一些事,但是這是存在的所以需要做一些事情來防止對關(guān)鍵代碼的重排序
  • 以下代碼中主線程中的代碼可能存在重排序,即在缺少同步情況下,JVM允許編譯器和CPU對操作順序進(jìn)行重排序,那么最后 number = 42; ready = true;語句的執(zhí)行順序就會顛倒變?yōu)? ready = true;number = 42; 。這對ReaderThread線程來說是個悲劇,因?yàn)樗赡芟茸x到ready=true,在執(zhí)行還沒等主線程為number設(shè)置42,就執(zhí)行了輸出number操作,結(jié)果就為0。那么這種結(jié)果不是我期望的42結(jié)果。這就是由于多個線程之間對內(nèi)存寫入操作的不可見導(dǎo)致的結(jié)果。實(shí)現(xiàn)內(nèi)存可見性如上面所示。
public class NoVisibility {
    private static boolean ready;
    private static int number;

    private static class ReaderThread extends Thread {
        public void run() {
            while (!ready)
                Thread.yield();
            System.out.println(number);
        }
    }

    public static void main(String[] args) {
        new ReaderThread().start();
        number = 42;
        ready = true;
    }
}

深入理解Java內(nèi)存模型(二)——重排序

  • java提供了volatile和synchronized兩個關(guān)鍵字來保證線程之間操作的有序性

正確性

某個類的行為與其規(guī)范完全一致。在良好的規(guī)范中通常會定義各種不變性條件(Invariant)來約束對象的狀態(tài)s,以及定義各種后驗(yàn)條件(Postcondition)來描述對象操作的結(jié)果。

  • 補(bǔ)充:不變性條件可能涉及對象的多個狀態(tài),比如,對象的a狀態(tài)變化時b也要變化,如果這個不變性在多線程中會被破壞了則該類不是線程安全的,因此當(dāng)不變性臺條件涉及多個變量時,當(dāng)更新某一個變量時,需要在同一個原子操作中對其它變量同時進(jìn)行更新。可用鎖實(shí)現(xiàn)。

非原子性的64位操作

內(nèi)存可見性和原子性:Synchronized和Volatile的比較

Java內(nèi)存模型要求,變量的讀取操作和寫入操作必須時原子操作,但對于非volatile類型的long和double變量,JVM允許將64位的讀操作和寫操作分解為兩個32位的操作,當(dāng)讀取一個非volatile類型的long變量時,如果對該變量的讀操作和寫操作在不同的線程中執(zhí)行,那么很可能會讀取到某個值的高32位和另一個值的低32位,因此在多線程中使用共享且可變(某個線程會對該變量執(zhí)行寫操作)的long和double等類型的bain了也是不安全的,除非使用volatile來聲明他們或者使用鎖保護(hù)起來。(也許以后的處理器就都可以提供64位數(shù)值的原子操作)

volatile變量

  • java語言提供一種稍弱的同步機(jī)制,即volatile變量,用來確保將變量的更新操作通知到其它線程。當(dāng)把變量聲明位volatile類型后,編譯器運(yùn)行時都會注意到這個變量是共享的,因此不會將該變量上的操作與其他內(nèi)存操作一起重排。voldatilte變量不會被緩存到寄存器或者對其他處理器不可見的地方,因此在讀取volatile類型變量時總會返回最新寫入的值。

  • 什么叫將變量的更新操作通知到其它線程?首先該變量是一個共享變量,可以被多個線程訪問,就是上面講的內(nèi)存可見性

  • 不要過度使用volatile變量,僅當(dāng)volatile變量能簡化代碼的實(shí)現(xiàn)以及對同步策略的驗(yàn)證時,才應(yīng)該使用他們,如果在驗(yàn)證正確性時(某個類的行為與其規(guī)范完全一致)需要對可見性進(jìn)行復(fù)雜的判斷,那么就不要使用volatile變量。

  • volatile變量的一種典型用法:檢查某個狀態(tài)標(biāo)記以判斷是否退出循環(huán)。下面示例中,線程通過數(shù)綿羊的方法進(jìn)入休眠。為了使這個示例能正確執(zhí)行,asleep必須為volatile變量。否則當(dāng)asleep被另一個線程修改時,執(zhí)行判斷的線程卻發(fā)現(xiàn)不了。為什么發(fā)現(xiàn)不了?答:JVM在server模式中(另一個模式client做了相對較少的優(yōu)化)對代碼進(jìn)行了更多的優(yōu)化,其中就包括將循環(huán)中未被修改的變量提升到循環(huán)外部,對于該代碼中,asleep在while中沒有被修改,如果asleep不是volatile類型,那么JVM就會將asleep的判斷條件提升到循環(huán)體外部,這將導(dǎo)致一個無線循環(huán)。

public class CountingSheep {
    volatile boolean asleep;
    void tryToSleep() {
        while (!asleep)
            countSomeSheep();
    }
    void countSomeSheep() {
        // One, two, three...
    }
}
  • volatile變量只能確保可見性,不能確保原子性,而加鎖機(jī)制兩種都可以。因?yàn)関olatile的語義不足以確保遞增操作(count++)的原子性,除非你能確保只有一個線程對變量執(zhí)行寫操作。在訪問volatile變量時不會執(zhí)行加鎖操作,因?yàn)橐簿筒粫?zhí)行線程阻塞。

  • 當(dāng)且僅當(dāng)滿足以下條件時才使用volatile變量:

    • 對變量的寫入操作不依賴變量的當(dāng)前值,或者你能確保只有單個線程更新變量的值。
    • 該變量不會與其它狀態(tài)變量一起納入不變性條件中。(在良好的規(guī)范中通常會定義各種不變性條件Invariant來約束對象的狀態(tài)s,以及定義各種后驗(yàn)條件Postcondition來描述對象操作的結(jié)果,)
    • 在訪問變量時不需要枷鎖。
  • 補(bǔ)充不變性條件:不變性條件可能涉及對象的多個狀態(tài),比如,對象的a狀態(tài)變化時b也要變化,如果這個不變性在多線程中會被破壞了則該類不是線程安全的,因此當(dāng)不變性臺條件涉及多個變量時,當(dāng)更新某一個變量時,需要在同一個原子操作中對其它變量同時進(jìn)行更新。可用鎖實(shí)現(xiàn)。

    • 在LinkedList集合中存在多個不變性條件,其中一條如下:鏈表的第一個節(jié)點(diǎn)指針first和最后一個節(jié)點(diǎn)指針last的不變性關(guān)系。
/**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

細(xì)說Java多線程之內(nèi)存可見性-視頻

發(fā)布與逸出

  • 發(fā)布一個對象:是對象能能在當(dāng)前作用域之外的代碼中使用。當(dāng)發(fā)布一個對象可能會間接地發(fā)布其他對象。當(dāng)發(fā)布一個對象時,在該對象的非私有域中引用的所有對象同樣會被發(fā)布。
  • 逸出:當(dāng)某個不應(yīng)該被發(fā)布的對象被發(fā)布時,這種情況稱為逸出。
  • 發(fā)布對象方法:
  • 將對象的引用保持到一個公有靜態(tài)變量中。
  • 指向該對象的應(yīng)用保持到其它代碼可以訪問的地方
  • 在一個非私有方法中返回對象的引用。
  • 發(fā)布一個內(nèi)部的類實(shí)例。如下代碼:ThisEscape 發(fā)布EventListener時,也隱含的發(fā)布了ThisEscape實(shí)例本身,因?yàn)檫@個內(nèi)部類實(shí)例中保護(hù)了對EventListener實(shí)例的隱含引用。這種非期望的發(fā)布就造成了ThisEscape對象的逸出。
public class ThisEscape {
    public ThisEscape(EventSource source) {
        source.registerListener(new EventListener() {
            public void onEvent(Event e) {
                doSomething(e);
            }
        });
    }
    void doSomething(Event e) {
    }
    interface EventSource {
        void registerListener(EventListener e);
    }
    interface EventListener {
        void onEvent(Event e);
    }
    interface Event {
    }
}
  • 防止逸出:
    不要在構(gòu)造過程中使用this引用。
    如果想著構(gòu)造函數(shù)中注冊一個事件監(jiān)聽器或者啟動線程,那么可以使用一個私有的構(gòu)造函數(shù)和一個公共的工廠方法。如下:
public class SafeListener {
    private final EventListener listener;

    private SafeListener() {
        listener = new EventListener() {
            public void onEvent(Event e) {
                doSomething(e);
            }
        };
    }
    public static SafeListener newInstance(EventSource source) {
        SafeListener safe = new SafeListener();
        source.registerListener(safe.listener);
        return safe;
    }
    void doSomething(Event e) {
    }
    interface EventSource {
        void registerListener(EventListener e);
    }
    interface EventListener {
        void onEvent(Event e);
    }
    interface Event {
    }
}

競態(tài)條件與復(fù)合操作

  • 當(dāng)某個計(jì)算的正確性取決于多個線程的交替執(zhí)行時序時,那么就會發(fā)生競態(tài)條件。出現(xiàn)競態(tài)條件,就可能會造成線程不安全。
    下面代碼就顯示了延遲初始化中的競態(tài)條件,它破壞了這個類的正確性。
public class LazyInitRace {
    private ExpensiveObject instance = null;

    public ExpensiveObject getInstance() {
        if (instance == null)
            instance = new ExpensiveObject();
        return instance;
    }
}

以下代碼存在競態(tài)條件,count++包含了”讀取-修改-寫入”三個操作。

public class UnsafeCountingFactorizer extends GenericServlet implements Servlet {
    private long count = 0;

    public long getCount() {
        return count;
    }

    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = factor(i);
        ++count;
        encodeIntoResponse(resp, factors);
    }
....
}
  • 常見競態(tài)條件:先檢查后執(zhí)行、讀取-修改-寫入。
  • 避免競態(tài)條件產(chǎn)生的線程不安全問題,這些操作應(yīng)該是原子性的。即為了確保線程安全性,把”先檢查后執(zhí)行“、”讀取-修改-寫入“等操作統(tǒng)稱為復(fù)合操作:包含了一組必須以原子方式執(zhí)行的操作。
  • 實(shí)現(xiàn)復(fù)合操作的原子性:枷鎖機(jī)制(可實(shí)現(xiàn)多個狀態(tài)的原子操作)、原子變量類(針對只有一個狀態(tài))
  • 為了實(shí)現(xiàn)這種復(fù)合操作的原子性可以使用加鎖機(jī)制。對于一些只包含一個狀態(tài)的復(fù)合操作可以使用java.until.concurrent.atomoc包中包含的一些原子變量類來解決。
  • java.until.concurrent.atomoc包中包含的一些原子變量類,用于實(shí)現(xiàn)在數(shù)值和對象引用上的原子狀態(tài)轉(zhuǎn)換。

如下代碼:使用AtomicLong來代替long類型的計(jì)數(shù)器,能夠確保所有對計(jì)數(shù)器狀態(tài)的訪問都是原子的,

public class CountingFactorizer extends GenericServlet implements Servlet {
    private final AtomicLong count = new AtomicLong(0);

    public long getCount() { return count.get(); }
    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = factor(i);
        count.incrementAndGet();
        encodeIntoResponse(resp, factors);
    }
}

線程封閉

  • 定義:當(dāng)訪問共享的可變數(shù)據(jù)時,通常需要使用同步。一種避免使用同步的方式就是不共享數(shù)據(jù)。如果僅在單線程內(nèi)訪問數(shù)據(jù),就不需要同步,這種技術(shù)被稱為線程封閉技術(shù)(Thread Confinement),它是實(shí)現(xiàn)現(xiàn)在安全性的最簡單方式之一。

  • 案例:JDBC的Connection對象就使用了線程封閉技術(shù),JDBC規(guī)范并不要求Connection對象必須是線程安全的。在典型的服務(wù)器應(yīng)用程序中,線程從連接池中獲得一個Connection對象,并且用該對象來處理請求,使用完后再將對象返還給連接池。由于大多數(shù)請求(如Servlet)都是有單個線程采用同步技術(shù)的方式來處理,并且再Connection對象返回之前,連接池不會再將他分配給其它線程,因此這中連接管理模式再處理請求時隱含的將Connection對象封裝再線程中。
    Servlet的多線程和線程安全

  • 注:應(yīng)用程序服務(wù)器提供的連接池是線程安全的連接池通常會由多個線程訪問,因此非線程安全的連接池是毫無意義的。

  • 實(shí)現(xiàn)線程封閉性的技術(shù):Java沒有強(qiáng)制規(guī)定某個變量必須有鎖來保護(hù),同樣也無法強(qiáng)制將對象封裝再某個線程中。線程封閉是再程序設(shè)計(jì)中考慮的一個因素。但是Java語言及其核心類庫提供了一些機(jī)制來幫助維持線程封閉性,例如棧封閉和ThreadLocal類,還有一種是Ad-hoc線程封閉,即便如此程序員也需要負(fù)責(zé)確保封閉性在線程中的對象不會從線程中逸出。

Ad-hoc線程封閉(脆弱,不推薦)

維護(hù)線程封閉性的職責(zé)完全有程序?qū)崿F(xiàn)來承擔(dān),很脆弱不建議使用。
如下代碼通過Map實(shí)現(xiàn)線程封閉性:
其中static類型的data是線程間共享的,但是為了實(shí)現(xiàn)數(shù)據(jù)和線程綁定,所以通過map來存放不同線程操作的數(shù)據(jù)data,data的獲取和線程也是綁定的,這樣就實(shí)現(xiàn)了data數(shù)據(jù)在單線程中訪問了,不會與其它線程共享,注map是和其它線程共享的, 這樣每個線程中操作的A、B類都是共享和本線程綁定的那個data,從而不會沖突和出錯。

Ad-hoc線程封閉

棧封閉性

  • 棧封閉性是線程封閉性的一種特例,再棧封閉中,只能通過局部變量才能訪問對象。
  • 局部變量固有屬性之一就是封閉在執(zhí)行線程中。它們位于執(zhí)行線程的棧中,其他線程無法訪問這各棧。棧封閉(也被稱為線程內(nèi)部使用或者線程局部局部使用,不要與核心類庫中的ThreadLocal混淆)比Ad-hoc線程封閉更易維護(hù)。
  • 如下:對于loadTheArk方法的局部變量numPairs,無論如何也不會破環(huán)棧的封閉性。因?yàn)槿魏畏椒ǘ紵o法獲得對基本類型的引用,因此java語言的這種語義就確保了基本類型的局部變量始終封閉在線程內(nèi)。
  • 在維護(hù)對象引用的棧封閉性時,程序員需要確保被引用的對象不會逸出。loadTheArk方法中animals引用指向了一個SortedSet對象,此時只有一個引用指向了集合animals,這個引用被封閉在了局部變量中,因此也被封閉在執(zhí)行線程中。但是,如果發(fā)布了對集合animals的引用,那么封閉性也被破壞,并導(dǎo)致對象animals逸出。
public int loadTheArk(Collection<Animal> candidates) {
        SortedSet<Animal> animals;
        int numPairs = 0;
        Animal candidate = null;

        // animals confined to method, don't let them escape!
        animals = new TreeSet<Animal>(new SpeciesGenderComparator());
        animals.addAll(candidates);
        for (Animal a : animals) {
            if (candidate == null || !candidate.isPotentialMate(a))
                candidate = a;
            else {
                ark.load(new AnimalPair(candidate, a));
                ++numPairs;
                candidate = null;
            }
        }
        return numPairs;
    }

ThreadLocal類(重點(diǎn))

  • 這個類能使線程中的某個值與保存該值的對象關(guān)聯(lián)起來。ThreadLocal提供類get與set等訪問接口或方法,這些方法為每個使用該變量的線程都存有一份獨(dú)立的副本,因此get總是返回由當(dāng)前執(zhí)行線程在調(diào)用set時設(shè)置的最新值。
  • ThreadLocal對象通常用于防止對可變的單實(shí)例變量(Singleton)或全局變量進(jìn)行共享。如:單線程應(yīng)用中可能會位置一個全局的數(shù)據(jù)庫連接,并在程序啟動時初始化這個連接對象,從而避免在調(diào)用每個方法時都需要傳遞一個Connection對象(實(shí)現(xiàn)線程內(nèi)數(shù)據(jù)共享)。
  • 如下代碼,通過將JDBC的連接保存到ThreadLocal對象中,每個線程都會擁有屬于自己的連接。
public class ConnectionDispenser {
    static String DB_URL = "jdbc:mysql://localhost/mydatabase";

    private ThreadLocal<Connection> connectionHolder
            = new ThreadLocal<Connection>() {
                public Connection initialValue() {
                    try {
                        return DriverManager.getConnection(DB_URL);
                    } catch (SQLException e) {
                        throw new RuntimeException("Unable to acquire Connection, e");
                    }
                };
            };

    public Connection getConnection() {
        return connectionHolder.get();
    }
}
  • 更多案例:1.如果需要將一個單線程應(yīng)用移植到多線程環(huán)境中,通過將共享的全局變量轉(zhuǎn)換為ThreadLocal(如果全局變量的語義允許),可以維持線程安全性。2.在EJB調(diào)用期間,J2EE容器需要將一個事務(wù)上下文(Transaction Context)與某個執(zhí)行中的線程關(guān)聯(lián)起來。通過將Transaction Context保存在靜態(tài)的ThreadLocal對象中,可以很容易實(shí)現(xiàn)這個功能。

  • 實(shí)現(xiàn)機(jī)制:可以將ThreadLocal<T>視為包含了Map<Thread,T>對象,其中保存了特定于該線程的值,但ThreadLocal的實(shí)現(xiàn)并非如此,這些特定于線程的值保存在Thread對象中,當(dāng)線程終止后,這些值會作為垃圾回收。

  • 注意:不要濫用ThreadLocal,例如將所有全局變量都作為ThreadLocal變量,或者作為“隱藏”方法參數(shù)的手段(設(shè)為全局就不需要通過參數(shù)傳遞過來)。ThreadLocal變量類似全局變量,它降低了代碼的可重用性,并在類之間引入隱含的耦合性(一個線程中或涉及操作多個類,這些類中有的方法就有可能依賴ThreadLocal變量),因此要格外小心。

線程封閉性、線程內(nèi)數(shù)據(jù)共享

  • 一個線程T1內(nèi)操作多個對象A、B時,A、B中操作的數(shù)據(jù)都屬于該線程范圍內(nèi)的。

  • 比如:javaWeb中存錢操作,會操作數(shù)據(jù)庫。

  • 張三開啟T1線程獲取連接connection,然后T1內(nèi)操作取錢類A取錢,操作記錄類B記錄日志,然后進(jìn)行conn提交。

  • 李四開啟T2線程獲取連接connection,然后T1內(nèi)操作取錢類A取錢,操作記錄類B記錄日志,然后進(jìn)行conn提交。

  • 線程間獨(dú)立:以上兩個線程獲取的connection應(yīng)該是獨(dú)立的,只屬于該線程,如果T1和T2共享一個connection,那么如果張三轉(zhuǎn)入錢后還沒來的急轉(zhuǎn)出,就被李四提前轉(zhuǎn)出了,那么就會出錯。 (即實(shí)現(xiàn)線程封閉性)

  • 線程內(nèi)共享:每個線程中的connection對象是對該線程中所有被操作對象都是共享的。

Paste_Image.png

不變性

  • 滿足同步需求的另一種方法是使用不可變對象(Immutable Object).

  • 當(dāng)滿足以下條件,對象才是不可變的:

  • 對象創(chuàng)建以后其狀態(tài)就不能修改。(比如:可通過關(guān)鍵字-》簡單類型狀態(tài)、程序控制實(shí)現(xiàn)-》引用類型狀態(tài))

  • 對象的所有域都是final類型(有例外)。

  • 對象是正確常見的(在創(chuàng)建對象期間,this引用沒有逸出)

  • 不可變性并不等于將對象中所有的域都聲明為final類型,即使都為final類型,這個對象也仍然是可變的,因?yàn)閒inal域可以保存可變對象的引用。

  • 如下代碼:在不可變對象基礎(chǔ)上構(gòu)建不可變類,盡管Set對象是可變的,但從ThreeStooges設(shè)計(jì)中可以看到,在Set對象構(gòu)造完成后,無法對其進(jìn)行修改。(程序控制)

public final class ThreeStooges {
    private final Set<String> stooges = new HashSet<String>();

    public ThreeStooges() {
        stooges.add("Moe");
        stooges.add("Larry");
        stooges.add("Curly");
    }
    public boolean isStooge(String name) {
        return stooges.contains(name);
    }
    public String getStoogeNames() {
        List<String> stooges = new Vector<String>();
        stooges.add("Moe");
        stooges.add("Larry");
        stooges.add("Curly");
        return stooges.toString();
    }
}
  • 這時候你可能會郁悶,沒有不將域聲明為final也可以啊,為什么要設(shè)為final。答:1.(自己理解)final域能確保初始化過程的安全性。2.其次通過將域聲明為final類型,也相當(dāng)于告訴維護(hù)人員這些域是不會變化的。3.良好的編程習(xí)慣。

Final域

  • fianl類型的域是不能修改的,但是如果final域所引用的對象是可變的,那么這些被引用的對象是可以修改的。
  • 在JMM中,final域能確保初始化過程的安全性

安全發(fā)布

不正確的發(fā)布:正確的對象被破壞

final域的內(nèi)存語義

不可變對象與初始化安全性

安全發(fā)布的常用模式

詳解Java中的clone方法 -- 原型模式
string 在clone()中的特殊性 (轉(zhuǎn)載)

--------待更新

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

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