集合框架 (第 12 篇) 源碼分析:jdk1.8版 ConcurrentHashMap

一藻三、集合框架源碼分析

原文持續(xù)更新鏈接: https://github.com/about-cloud/JavaCore

正文


:relaxed:本文是基于 jdk1.8.0_151 分析

如果你已經(jīng)閱讀了我之前寫的關(guān)于 jdk1.8 HashMapjdk1.7 ConcurrentHashMap 文章醉顽,對于本篇 jdk1.8版ConcurrentHashMap 源碼分析更容量理解沼溜,不過沒關(guān)系,本篇文章也非常詳細地來分析游添。

一系草、ConcurrentHashMap的擴展關(guān)系

public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
        implements ConcurrentMap<K, V>, Serializable

從擴展關(guān)系上,沒有任何變化唆涝,但 ConcurrentMap<K, V> 又添加幾個 default 方法找都,default 的好處在上篇文章中已經(jīng)提到過:

/**
 * forEach 迭代方法
 *
 * @throws NullPointerException {@inheritDoc}
 * @since 1.8
 */
default void forEach(BiConsumer<? super K, ? super V> action) {
    Objects.requireNonNull(action);
    for (Map.Entry<K, V> entry : entrySet()) {
        K k;
        V v;
        try {
            k = entry.getKey();
            v = entry.getValue();
        } catch(IllegalStateException ise) {
            continue;
        }
        action.accept(k, v);
    }
}
/** 其他略 */

??????

二、ConcurrentHashMap數(shù)據(jù)結(jié)構(gòu)

回顧 jdk 1.7 的 ConcurrentHashMap 的數(shù)據(jù)結(jié)構(gòu)

jdk 1.7ConcurrentHashMap是基于 分段鎖 機制設(shè)計的廊酣,將一個大的Map分割成n個小的 段segment能耻,對每段進行加鎖,降低了容器加鎖的粒子度亡驰,每段(segment)各自加鎖晓猛,互不影響,當(dāng)一個線程訪問 Map 其中一段數(shù)據(jù)時凡辱,其他段的數(shù)據(jù)也能被線程正常訪問戒职。分段鎖使用的鎖是 ReentrantLock 可重入鎖。

ConcurrentHashMap1.7v

優(yōu)化后的 jdk 1.8 的 ConcurrentHashMap 的數(shù)據(jù)結(jié)構(gòu)

jdk 1.8ConcurrentHashMap 相對于較前的版本該動還是蠻大透乾。

首先取消了 分段鎖 的設(shè)計洪燥,而改用像 jdk 1.8HashMap 那樣的數(shù)據(jù)結(jié)構(gòu):數(shù)組 + 鏈表 + 紅黑樹(但依然保留著 分段鎖 的這種設(shè)計思想)磕秤。

再次,在保證線程安全的問題了取消了 ReentrantLock 改用 CAS + synchronized 保證并發(fā)的安全蚓曼。(當(dāng)然ReentrantLock 的原理也是基于CAS的)亲澡,在使用 synchronized 時并沒有像 Hashtable 那樣粗暴地鎖住整個數(shù)組钦扭,而它是鎖住單個節(jié)點纫版。

jdk1.8 ConcurrentHashMap
jdk1.8 ConcurrentHashMap

??重要的字段

/** 最大容量 10.7億+ */
private static final int MAXIMUM_CAPACITY = 1 << 30;
/** table的默認初始容量 16,容量必須為 2 的次冪 */
private static final int DEFAULT_CAPACITY = 16;
/** table 默認并發(fā)等級 16 */
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
/** table的默認負載因子 0.75客情,可以通過構(gòu)造函數(shù)指定負載因子大小 */
private static final float LOAD_FACTOR = 0.75f;
/** (從鏈表上的元素個數(shù)來講)鏈表轉(zhuǎn)紅黑樹的閾值 */
static final int TREEIFY_THRESHOLD = 8;
/** (從紅黑樹上的元素個數(shù)來講)紅黑樹轉(zhuǎn)鏈表的閾值 */
static final int UNTREEIFY_THRESHOLD = 6;
/** 鏈表轉(zhuǎn)紅黑樹的另一個閾值(條件):Map的容量至少為64. */
static final int MIN_TREEIFY_CAPACITY = 64;
/**  每次進行轉(zhuǎn)移的最小值 */
private static final int MIN_TRANSFER_STRIDE = 16;
/** 生成sizeCtl所使用的bit位數(shù) */
private static int RESIZE_STAMP_BITS = 16;
/** 進行擴容所允許的最大線程數(shù) */
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
/** 可以有效使用的 CPU 數(shù)量 */
static final int NCPU = Runtime.getRuntime().availableProcessors();

/** 首先哈希值不小于0其弊,下面的幾個常量相當(dāng)于標識位 */
/** 用于轉(zhuǎn)換節(jié)點的哈希值 */
static final int MOVED     = -1;
/** 用于紅黑樹根節(jié)點的哈希碼的標識位 */
static final int TREEBIN   = -2;
/** 容器中還有一個保留節(jié)點,此處也是有關(guān)的哈希碼的標識位 */
static final int RESERVED  = -3;
/** 用于普通節(jié)點哈希碼的標識位 */
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash

/** 容器的數(shù)組(哈希表膀斋、散列表)梭伐,在第一次插入元素時才初始化,大小總是2的次冪 */
transient volatile Node<K,V>[] table;
/** 擴容時使用的臨時過度的數(shù)組(僅使用于擴容) */
private transient volatile Node<K,V>[] nextTable;
/** 容器中實際存儲元素的數(shù)量仰担。(利用CAS自旋來更新其值) */
private transient volatile long baseCount;
/**
 * 不同的值起到不同的作用糊识,但唯一功能是起到控制作用,你可以認為它是控制標識符
 * sizeCtl小于0 標識容器正在初始化或擴容
 * sizeCtl為-1 標識正式初始化
 * sizeCtl為-N 標識有N-1個線程正在進行擴容操作
 */
private transient volatile int sizeCtl;
/** 調(diào)整表大小時使用摔蓝,下一個transfer任務(wù)的起始下標index 加上1 之后的值 */
private transient volatile int transferIndex;
/** 當(dāng)初始化或者擴容時赂苗,CAS自旋鎖的標識位 */
private transient volatile int cellsBusy;
/** 計數(shù)器池,非空時贮尉,大小為2的次冪 */
private transient volatile CounterCell[] counterCells;

三拌滋、ConcurrentHashMap中各種數(shù)據(jù)節(jié)點

jdk1.8 ConcurrentHashMap 中數(shù)據(jù)節(jié)點的種類比較多,比如 Node<K, V>猜谚、TreeNode<K, V>败砂、TreeBin<K, V>ReservationNode<K,V>魏铅。正式這種獨具特色的設(shè)計昌犹,才擁有高性能的Map并發(fā)容器。

大多數(shù)用于存儲元素的 Node<K, V> 節(jié)點(鏈表必用的節(jié)點)

ConcurrentHashMap 重點元素 項HashEntry<K, V>jdk1.8 已改為 節(jié)點Node<K, V>览芳,與 jdk1.7版本的自定義略有不同祭隔,jdk1.8 中是實現(xiàn)于 Map.Entry 接口的。 下面就來分析一下 jdk1.8ConcurrentHashMap Node<K, V> 節(jié)點:

??請注意:這個Node<K, V>大多數(shù) 用于存儲的元素節(jié)點路操,并不是全部疾渴,而紅黑樹 是用下面的 TreeNode<K, V> 節(jié)點作為元素存儲節(jié)點。因為 鏈表 中的節(jié)點只有一個后繼節(jié)點屯仗,而 TreeNode<K, V> 作為二叉樹中的節(jié)點搞坝,最多可有兩個后繼節(jié)點(既左、右子節(jié)點)魁袜。

可參考往期的文章https://github.com/about-cloud/JavaCore

/** 實現(xiàn)于 Map.Entry */
static class Node<K,V> implements Map.Entry<K,V> {
    // // final 修飾的 哈希碼桩撮、key敦第,防止被重復(fù)賦值
    final int hash;
    final K key;
    // 具有可見性的 val 和 next
    volatile V val;
    // 當(dāng)前節(jié)點指向的下一個節(jié)點
    volatile Node<K,V> next;

    /**
     * 構(gòu)造方法用于注入 node節(jié)點 的屬性值(或引用)
     * 參數(shù)從左至右依次是:key的哈希碼,key店量,value芜果,指向的下一個節(jié)點next
     */
    Node(int hash, K key, V val, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.val = val;
        this.next = next;
    }
    // getter & toString 方法
    public final K getKey()       { return key; }
    public final V getValue()     { return val; }
    public final String toString(){ return key + "=" + val; }
    // 返回節(jié)點的哈希碼
    public final int hashCode()   { return key.hashCode() ^ val.hashCode(); }
    // 設(shè)置 value 的方法,可能會拋出不支持的操作異常
    public final V setValue(V value) {
        throw new UnsupportedOperationException();
    }

    // 用于節(jié)點比較是否相等的方法
    public final boolean equals(Object o) {
        Object k, v, u; Map.Entry<?,?> e;
        // 返回判斷key融师、value是否相同的結(jié)果
        return ((o instanceof Map.Entry) &&
                (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
                (v = e.getValue()) != null &&
                (k == key || k.equals(key)) &&
                (v == (u = val) || v.equals(u)));
    }

    /**
     * 虛擬化地支持 map.get() 操作; 子類可以重寫此方法.
     */
    Node<K,V> find(int h, Object k) {
        Node<K,V> e = this;
        if (k != null) {
            // 循環(huán)遍歷鏈表
            do {
                K ek;
                if (e.hash == h &&
                    ((ek = e.key) == k || (ek != null && k.equals(ek))))
                    return e;
            } while ((e = e.next) != null);
        }
        return null;
    }
}
專用于紅黑樹的 TreeNode<K, V> 節(jié)點
static final class TreeNode<K,V> extends Node<K,V> {
    // 父節(jié)點
    TreeNode<K,V> parent;
    // 左子節(jié)點
    TreeNode<K,V> left;
    // 右子節(jié)點
    TreeNode<K,V> right;
    // 指向上一個節(jié)點(一般是父節(jié)點)右钾,刪除節(jié)點時會用到
    TreeNode<K,V> prev;
    // 紅黑標識:true表示此節(jié)點為紅色,false表示此節(jié)點為黑色
    boolean red;
    // 有參構(gòu)造方法
    TreeNode(int hash, K key, V val, Node<K,V> next,
             TreeNode<K,V> parent) {
        super(hash, key, val, next);
        this.parent = parent;
    }

    Node<K,V> find(int h, Object k) {
        return findTreeNode(h, k, null);
    }

    /**
     * 查找并返回紅黑樹中是存在的節(jié)點旱爆,不存在返回null
     * 在上篇關(guān)于jdk1.8HashMap源碼分析的文章中舀射,分析過類似的(寫)操作
     * 文章持續(xù)更新地址:https://github.com/about-cloud/JavaCore
     */
    final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) {
        if (k != null) {
            // 當(dāng)前節(jié)點
            TreeNode<K,V> p = this;
            // 循環(huán)遍歷平衡樹
            do  {
                // ph:當(dāng)前節(jié)點的哈希碼,
                // dir:搜索的方向怀伦,左或右:-1表示左脆烟,1表示右,
                // pk:當(dāng)前節(jié)點的key
                // q:當(dāng)前節(jié)點
                int ph, dir; K pk; TreeNode<K,V> q;
                // pl:當(dāng)前節(jié)點p的左子節(jié)點房待;pr:當(dāng)前節(jié)點p的右子節(jié)點
                TreeNode<K,V> pl = p.left, pr = p.right;
                if ((ph = p.hash) > h)
                    // 當(dāng)前節(jié)點的哈希值大于指定的哈希值邢羔,指向左子節(jié)點
                    p = pl;
                else if (ph < h)
                    // 當(dāng)前節(jié)點的哈希值小于指定的哈希值,指向右子節(jié)點
                    p = pr;
                else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
                    // 如果當(dāng)前節(jié)點的key與指定的k相同桑孩,那么就直接返回此節(jié)點
                    return p;
                else if (pl == null)
                    // 如果左子節(jié)點為空拜鹤,就向右子節(jié)點查找
                    p = pr;
                else if (pr == null)
                    // 如果右子節(jié)點為空,就向左子節(jié)點查找
                    p = pl;
                // 判斷 k 的類是否實現(xiàn)了比較器
                else if ((kc != null ||
                          // 判斷 k 的類是否實現(xiàn)了比較器
                          (kc = comparableClassFor(k)) != null) &&
                         // 這里實際是 pk == null || pk.getClass() != kc ? 0 :
                         // ((Comparable)pk).compareTo(pk)
                         // 下面是解讀這個三目運算:
                         // pk == null 表示判斷當(dāng)前節(jié)點是否為null
                         // pk.getClass() != kc 表示當(dāng)前節(jié)點對象的類和key的對象的類是否不同
                         // ((Comparable)k).compareTo(pk)表示將指定的key與當(dāng)前節(jié)點的key比較
                         (dir = compareComparables(kc, k, pk)) != 0)
                    // dir小于0表示向左子節(jié)點搜索
                    p = (dir < 0) ? pl : pr;
                // 循環(huán)查找
                else if ((q = pr.findTreeNode(h, k, kc)) != null)
                    return q;
                else
                    p = pl;
            } while (p != null);
        }
        return null;
    }
}

方法使用的位置 保留節(jié)點

static final class ReservationNode<K,V> extends Node<K,V> {
    ReservationNode() {
        super(RESERVED, null, null, null);
    }

    Node<K,V> find(int h, Object k) {
        return null;
    }
}
持有紅黑樹根節(jié)點的容器

TreeBin 是保證 ConcurrentHashMap 線程安全的重要數(shù)據(jù)結(jié)構(gòu)洼怔,它自身維護著讀/寫鎖署惯。

static final class TreeBin<K,V> extends Node<K,V> {
    // 紅黑樹的根節(jié)點
    TreeNode<K,V> root;
    // // 鏈表的頭節(jié)點(桶頂?shù)墓?jié)點)
    volatile TreeNode<K,V> first;
    // 最近一個設(shè)置 waiter 標識位的線程
    volatile Thread waiter;
    // 鎖狀態(tài)標識位
    volatile int lockState;
    static final int WRITER = 1; // 持有寫鎖時的狀態(tài)位
    static final int WAITER = 2; // 正在等待寫鎖的狀態(tài)位
    static final int READER = 4; // 設(shè)置讀鎖時的增量值
    
    ...其他方法跟 TreeNode 中的方法很相似
}

四钥飞、ConcurrentHashMap添加元素操作

面向用戶的 put 方法
public V put(K key, V value) {
    return putVal(key, value, false);
}
面向 put 和 putIfAbsent 多用途的添加元素的方法

真正實現(xiàn)用于 put 和 putIfAbsent 的添加方法

final V putVal(K key, V value, boolean onlyIfAbsent) {
    // 不允許key搬素、value為null
    // 將null值判斷提前,符合異常處理的規(guī)則(這里也是較上一版做了優(yōu)化)
    if (key == null || value == null) throw new NullPointerException();
    // 計算key的哈希碼
    int hash = spread(key.hashCode());
    // binCount 用來記錄鏈表中節(jié)點數(shù)量碘赖,進而判斷是否達到轉(zhuǎn)為紅黑樹的閾值
    int binCount = 0;
    // 遍歷table數(shù)組
    for (Node<K,V>[] tab = table;;) {
        // 要插入元素所在位置的節(jié)點
        Node<K,V> f;
        // n 表示數(shù)組長度安岂;i 表示索引轻猖;fh 表示要插入元素所在位置的節(jié)點的哈希碼
        int n, i, fh;
        if (tab == null || (n = tab.length) == 0)
            // 如果數(shù)組為null或者數(shù)組的大小為0,那么進行初始化數(shù)組
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            // 通過指定key的哈希碼找到對應(yīng)的節(jié)點域那,
            // 在節(jié)點為null的情況下咙边,通過CAS自旋方式將這個元素放入其中
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                // 如果通過CAS自旋成功添加元素,就直接跳出循環(huán)
                // 否則就進入下一個循環(huán)
                break;
        }
        else if ((fh = f.hash) == MOVED)
            // 標識著要遷移數(shù)據(jù)
            tab = helpTransfer(tab, f);
        // 通過上面過濾的條件次员,在應(yīng)該能猜到下面不是關(guān)于鏈表就是關(guān)于紅黑樹
        // 因為哈希槽的位置不為null
        else {
            // 舊值
            V oldVal = null;
            // 只給單個節(jié)點加鎖
            synchronized (f) {
                // 再次判斷節(jié)點
                if (tabAt(tab, i) == f) {
                    // 判斷節(jié)點f的哈希碼是否不小于0
                    if (fh >= 0) {
                        // 大于等于0意味著該處有元素败许,記錄元素加1
                        binCount = 1;
                        // 從桶頂(哈希槽)開始遍歷鏈表
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            // 判斷key是否相同
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                // 如果相同的話,就準備獲取value
                                oldVal = e.val;
                                // onlyIfAbsent為true標識可以覆蓋value淑蔚,false標識不允許覆蓋 
                                if (!onlyIfAbsent)
                                    // 如果允許覆蓋value市殷,就覆蓋value
                                    e.val = value;
                                break;
                            }
                            // 記錄下一個節(jié)點的上一個節(jié)點(目前以為著是當(dāng)前節(jié)點e)
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                // 如果當(dāng)前節(jié)點的下一個節(jié)點為null,就把元素添加到鏈表的尾部
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                // 添加完成后就直接跳出循環(huán)
                                break;
                            }
                        }
                    }
                    else if (f instanceof TreeBin) {
                        Node<K,V> p;
                        binCount = 2;
                        // 將元素添加到紅黑樹
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                       value)) != null) {
                            oldVal = p.val;
                            // 是否可以覆蓋已有的value
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }
            if (binCount != 0) {
                // 判斷在鏈表不為空的情況下刹衫,是否達到轉(zhuǎn)為紅黑樹的閾值
                if (binCount >= TREEIFY_THRESHOLD)
                    // 如果鏈表元素達到轉(zhuǎn)為紅黑樹的閾值醋寝,就轉(zhuǎn)為紅黑樹
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    // 添加計數(shù)
    addCount(1L, binCount);
    return null;
}
添加計數(shù) addCount

方法的實現(xiàn)很像 LongAdder

private final void addCount(long x, int check) {
    // 計數(shù)池
    CounterCell[] as;
    // b表示實際存放元素的數(shù)量搞挣;s表示添加元素后的數(shù)量
    long b, s;
    if ((as = counterCells) != null ||
        // 使用CAS自旋方式加上數(shù)量
        !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
        CounterCell a; long v; int m;
        boolean uncontended = true;
        if (as == null || (m = as.length - 1) < 0 ||
            (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
            !(uncontended =
              U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
            // 多線程下競爭失敗會走這里
            fullAddCount(x, uncontended);
            return;
        }
        if (check <= 1)
            return;
        s = sumCount();
    }
    if (check >= 0) {
        Node<K,V>[] tab, nt; int n, sc;
        while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
               (n = tab.length) < MAXIMUM_CAPACITY) {
            int rs = resizeStamp(n);
            if (sc < 0) {
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                    transferIndex <= 0)
                    break;
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                    transfer(tab, nt);
            }
            else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                         (rs << RESIZE_STAMP_SHIFT) + 2))
                transfer(tab, null);
            s = sumCount();
        }
    }
}

當(dāng)然后面還會持續(xù)更新本文,有興趣可以關(guān)注上面GitHub文章音羞。

原文持續(xù)更新鏈接: https://github.com/about-cloud/JavaCore

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末囱桨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子嗅绰,更是在濱河造成了極大的恐慌舍肠,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件办陷,死亡現(xiàn)場離奇詭異貌夕,居然都是意外死亡律歼,警方通過查閱死者的電腦和手機民镜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來险毁,“玉大人制圈,你說我怎么就攤上這事∨峡觯” “怎么了鲸鹦?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長跷跪。 經(jīng)常有香客問我馋嗜,道長,這世上最難降的妖魔是什么吵瞻? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任葛菇,我火速辦了婚禮,結(jié)果婚禮上橡羞,老公的妹妹穿的比我還像新娘眯停。我一直安慰自己,他們只是感情好卿泽,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布莺债。 她就那樣靜靜地躺著,像睡著了一般签夭。 火紅的嫁衣襯著肌膚如雪齐邦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天第租,我揣著相機與錄音措拇,去河邊找鬼。 笑死煌妈,一個胖子當(dāng)著我的面吹牛儡羔,可吹牛的內(nèi)容都是我干的宣羊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼汰蜘,長吁一口氣:“原來是場噩夢啊……” “哼仇冯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起族操,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤苛坚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后色难,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泼舱,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年枷莉,在試婚紗的時候發(fā)現(xiàn)自己被綠了娇昙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡笤妙,死狀恐怖冒掌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蹲盘,我是刑警寧澤股毫,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站召衔,受9級特大地震影響铃诬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜苍凛,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一趣席、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧毫深,春花似錦吩坝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至闸迷,卻和暖如春嵌纲,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背腥沽。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工逮走, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人今阳。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓师溅,卻偏偏與公主長得像茅信,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子墓臭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354

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