深入理解 volatile 關(guān)鍵字原理

1. 基本概念

volatile 關(guān)鍵字,具有兩個(gè)特性:1. 內(nèi)存的可見性, 2. 禁止指令重排序優(yōu)化。

內(nèi)存可見性

被 volatile 關(guān)鍵字修飾的變量,當(dāng)線程要對(duì)這個(gè)變量執(zhí)行的寫操作,都不會(huì)寫入本地緩存枢步,而是直接刷入主內(nèi)存中。當(dāng)線程讀取被 volatile 關(guān)鍵字修飾的變量時(shí)渐尿,也是直接從主內(nèi)存中讀取醉途。

注意:volatile 不能保證原子性。很多時(shí)候都會(huì)誤用砖茸。

下面是問題代碼:

public class VolatileDemo {

    static volatile int count = 0;

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    VolatileDemo.inc();
                }
            }).start();
        }
        System.out.println(VolatileDemo.count);
    }

    static void inc() {
        count++;
    }
}

很多人以為加上了 volatile 關(guān)鍵字就能夠?qū)崿F(xiàn)對(duì) int 變量的原子操作隘擎,事實(shí)并非這樣。上面代碼每次運(yùn)行的結(jié)果都不相同凉夯∏妒海看望上面這些基本概念推正,下面就開始深入理解下 volatile 這個(gè)關(guān)鍵字吧恍涂。

2. JVM 內(nèi)存模型

2.1 可見性

可見性宝惰,是指線程之間的可見性,一個(gè)線程修改的狀態(tài)對(duì)另一個(gè)線程是可見的再沧。也就是一個(gè)線程修改的結(jié)果尼夺。另一個(gè)線程馬上就能看到。比如:用 volatile 修飾的變量炒瘸,就會(huì)具有可見性淤堵。volatile 修飾的變量不允許線程內(nèi)部緩存和重排序,即直接修改內(nèi)存顷扩。所以對(duì)其他線程是可見的拐邪。但是這里需要注意一個(gè)問題,volatile 只能讓被他修飾內(nèi)容具有可見性隘截,但不能保證它具有原子性扎阶。

2.2 原子性

原子是世界上的最小單位,具有不可分割性婶芭。比如 a=0东臀;(a 非 long 和 double 類型) 這個(gè)操作是不可分割的,那么我們說這個(gè)操作時(shí)原子操作犀农。再比如:a++惰赋; 這個(gè)操作實(shí)際是 a = a + 1;是可分割的呵哨,所以他不是一個(gè)原子操作赁濒。java 的 concurrent 包下提供了一些原子類,我們可以通過閱讀 API 來了解這些原子類的用法孟害。比如:AtomicInteger拒炎、AtomicLong、AtomicReference等纹坐。

2.3 有序性

Java 語言提供了 volatile 和 synchronized 兩個(gè)關(guān)鍵字來保證線程之間操作的有序性枝冀,volatile 是因?yàn)槠浔旧戆敖怪噶钪嘏判颉钡恼Z義,synchronized 是由“一個(gè)變量在同一個(gè)時(shí)刻只允許一條線程對(duì)其進(jìn)行 lock 操作”這條規(guī)則獲得的耘子,此規(guī)則決定了持有同一個(gè)對(duì)象鎖的兩個(gè)同步塊只能串行執(zhí)行果漾。

3. volatile 原理

當(dāng)一個(gè)變量定義為 volatile 之后,將具備兩種特性:

  1. 保證此變量對(duì)所有的線程的可見性谷誓,這里的“可見性”绒障,如本文開頭所述,當(dāng)一個(gè)線程修改了這個(gè)變量的值捍歪,volatile 保證了新值能立即同步到主內(nèi)存户辱,以及每次使用前立即從主內(nèi)存刷新鸵钝。但普通變量做不到這點(diǎn),普通變量的值在線程間傳遞均需要通過主內(nèi)存來完成庐镐。(隨著虛擬機(jī)的優(yōu)化恩商,普通變量也可以具有可見性了,下面來看一個(gè)代碼)
public class VolatileDemo extends Thread {

    static boolean flag = false;

    @Override
    public void run() {
        while (!flag) {}
    }

    public static void main(String[] args) throws InterruptedException {
        new VolatileDemo().start();
        // 等一段時(shí)間必逆,目的是為了能夠讓線程啟動(dòng)并進(jìn)入到 run 方法里
        TimeUnit.MILLISECONDS.sleep(300);
        flag = true;
    }
}

上面這段代碼永遠(yuǎn)不會(huì)結(jié)束怠堪,因?yàn)閷?duì) flag 的修改是在 main 線程的本地工作內(nèi)存中的,flag 的值對(duì)其他線程不可見名眉。對(duì) flag 加上 volatile 修飾符在做測試粟矿,程序能夠正常結(jié)束退出。

public class VolatileDemo extends Thread {

    static volatile boolean flag = false;

    @Override
    public void run() {
        while (!flag) {}
    }

    public static void main(String[] args) throws InterruptedException {
        new VolatileDemo().start();
        // 等一段時(shí)間损拢,目的是為了能夠讓線程啟動(dòng)并進(jìn)入到 run 方法里
        TimeUnit.MILLISECONDS.sleep(300);
        flag = true;
    }
}

但是對(duì)上面這段代碼在稍作修改陌粹,發(fā)現(xiàn)其實(shí)也可以不用 volatile 關(guān)鍵字,普通變量照樣能夠?qū)崿F(xiàn)內(nèi)存可見性福压,程序也能夠正常退出掏秩。代碼如下:

public class VolatileDemo extends Thread {

    static boolean flag = false;

    @Override
    public void run() {
        while (!flag) { System.out.println(1); }
    }

    public static void main(String[] args) throws InterruptedException {
        new VolatileDemo().start();
        // 等一段時(shí)間,目的是為了能夠讓線程啟動(dòng)并進(jìn)入到 run 方法里
        TimeUnit.MILLISECONDS.sleep(300);
        flag = true;
    }
}

這是什么原因呢隧膏?原來只有在對(duì)變量讀取頻率很高的情況下哗讥,虛擬機(jī)才不會(huì)及時(shí)回寫主內(nèi)存,而當(dāng)頻率沒有達(dá)到虛擬機(jī)認(rèn)為的高頻率時(shí)胞枕,普通變量和volatile是同樣的處理邏輯杆煞。如在每個(gè)循環(huán)中執(zhí)行System.out.println(1)加大了讀取變量的時(shí)間間隔,使虛擬機(jī)認(rèn)為讀取頻率并不那么高腐泻,所以實(shí)現(xiàn)了和volatile的效果决乎。

  1. 禁止指令重排序優(yōu)化。有volatile修飾的變量派桩,賦值后多執(zhí)行了一個(gè) “l(fā)oad addl $0x0, (%esp)” 操作构诚,這個(gè)操作相當(dāng)于一個(gè)內(nèi)存屏障(指令重排序時(shí)不能把后面的指令重排序到內(nèi)存屏障之前的位置),只有一個(gè) CPU 訪問內(nèi)存時(shí)铆惑,并不需要內(nèi)存屏障

4. 深入理解指令重排序和內(nèi)存屏障

4.1 as-if-serial 語義

as-if-serial 的語義是:不管怎么重排序范嘱,單線程程序的執(zhí)行結(jié)果不能被改變。編譯器员魏、runtime和處理器都必須遵守“as-if-serial”語義丑蛤。

為了遵守as-if-serial語義,編譯器和處理器不會(huì)對(duì)存在數(shù)據(jù)依賴關(guān)系的操作做重排序撕阎,因?yàn)檫@種重排序會(huì)改變執(zhí)行結(jié)果受裹。

但是,如果操作之間不存在數(shù)據(jù)依賴關(guān)系,這些操作就可能被編譯器和處理器重排序棉饶。

拿個(gè)簡單例子來說:

public void execute(){
    int a=0;
    int b=1;
    int c=a+b;
}

這里a=0,b=1兩句可以隨便排序厦章,不影響程序邏輯結(jié)果,但c=a+b這句必須在前兩句的后面執(zhí)行照藻。

as-if-serial 語義把單線程程序保護(hù)了起來袜啃,遵守 as-if-serial 語義的編譯器、runtime 和處理器共同為編寫單線程程序的程序員創(chuàng)建了一個(gè)幻覺:單線程程序是按程序的順序來執(zhí)行的岩梳。as-if-serial 語義使單線程程序員無需擔(dān)心重排序會(huì)干擾他們囊骤,也無需擔(dān)心內(nèi)存可見性問題。

4.2 指令重排序(happens-before)

重排序的規(guī)則

★1. 程序次序規(guī)則(Program Order Rule):在一個(gè)線程內(nèi)冀值,按照代碼順序,書寫在前面的操作先行發(fā)生于書寫在后面的操作宫屠。準(zhǔn)確地說應(yīng)該是控制流順序而不是代碼順序列疗,因?yàn)橐紤]分支、循環(huán)等結(jié)構(gòu)浪蹂。

★2. 監(jiān)視器鎖定規(guī)則(Monitor Lock Rule):一個(gè)unlock操作先行發(fā)生于后面對(duì)同一個(gè)對(duì)象鎖的lock操作抵栈。這里強(qiáng)調(diào)的是同一個(gè)鎖,而“后面”指的是時(shí)間上的先后順序坤次,如發(fā)生在其他線程中的lock操作古劲。

★3. volatile變量規(guī)則(Volatile Variable Rule):對(duì)一個(gè)volatile變量的寫操作發(fā)生于后面對(duì)這個(gè)變量的讀操作,這里的“后面”也指的是時(shí)間上的先后順序缰猴。

  1. 線程啟動(dòng)規(guī)則(Thread Start Rule):Thread獨(dú)享的start()方法先行于此線程的每一個(gè)動(dòng)作产艾。

  2. 線程終止規(guī)則(Thread Termination Rule):線程中的每個(gè)操作都先行發(fā)生于對(duì)此線程的終止檢測,我們可以通過Thread.join()方法結(jié)束滑绒、Thread.isAlive()的返回值檢測到線程已經(jīng)終止執(zhí)行闷堡。

  3. 線程中斷規(guī)則(Thread Interruption Rule):對(duì)線程interrupte()方法的調(diào)用優(yōu)先于被中斷線程的代碼檢測到中斷事件的發(fā)生,可以通過Thread.interrupted()方法檢測線程是否已中斷疑故。

  4. 對(duì)象終結(jié)原則(Finalizer Rule):一個(gè)對(duì)象的初始化完成(構(gòu)造函數(shù)執(zhí)行結(jié)束)先行發(fā)生于它的finalize()方法的開始杠览。

★8. 傳遞性(Transitivity):如果操作A先行發(fā)生于操作B,操作B先行發(fā)生于操作C纵势,那就可以得出操作A先行發(fā)生于操作C的結(jié)論踱阿。

如果我們的多線程程序依賴于代碼書寫順序钦铁,那么就要考慮是否符合以上規(guī)則,如果不符合就要通過一些機(jī)制使其符合育瓜,最常用的就是synchronized、Lock以及volatile修飾符躏仇。

值得注意的是:兩個(gè)操作之間具有happens-before關(guān)系腺办,并不意味著前一個(gè)操作必須要在后一個(gè)操作之前執(zhí)行糟描! happens-before 僅僅要求前一個(gè)操作(執(zhí)行的結(jié)果)對(duì)后一個(gè)操作可見,且前一個(gè)操作按順序排在第二個(gè)操作之前船响。

舉一個(gè)例子來理解重排序,看下面的代碼:

public class SimpleHappenBefore {
    /** 這是一個(gè)驗(yàn)證結(jié)果的變量 */
    private static int a=0;
    /** 這是一個(gè)標(biāo)志位 */
    private static boolean flag=false;
    public static void main(String[] args) throws InterruptedException {
        //由于多線程情況下未必會(huì)試出重排序的結(jié)論,所以多試一些次
        for(int i=0;i<1000;i++){
            ThreadA threadA=new ThreadA();
            ThreadB threadB=new ThreadB();
            threadA.start();
            threadB.start();
            //這里等待線程結(jié)束后,重置共享變量,以使驗(yàn)證結(jié)果的工作變得簡單些.
            threadA.join();
            threadB.join();
            a=0;
            flag=false;
        }
    }
    static class ThreadA extends Thread{
        public void run(){
            a=1;            //1
            flag=true;      //2
        }
    }
    static class ThreadB extends Thread{
        public void run(){
            if(flag){       //3
                a=a*1;      //4
            }
            if(a==0){
                System.out.println("ha,a==0");
            }
        }
    }
}

flag 變量是個(gè)標(biāo)記见间,用來標(biāo)識(shí)變量 a 是否已被寫入。這里假設(shè)有兩個(gè)線程 A 和 B米诉,A 首先執(zhí)行 writer() 方法,隨后 B 線程接著執(zhí)行 reader() 方法史侣。線程 B 在執(zhí)行操作 4 時(shí)拴泌,能否看到線程 A 在操作 1 對(duì)共享變量 a 的寫入?

答案是:不一定能看到。

由于操作1和操作2沒有數(shù)據(jù)依賴關(guān)系惊橱,編譯器和處理器可以對(duì)這兩個(gè)操作重排序蚪腐;同樣,操作3和操作4沒有數(shù)據(jù)依賴關(guān)系税朴,編譯器和處理器也可以對(duì)這兩個(gè)操作重排序回季。讓我們先來看看,當(dāng)操作1和操作2重排序時(shí)掉房,可能會(huì)產(chǎn)生什么效果茧跋?請(qǐng)看下面的程序執(zhí)行時(shí)序圖:


image

如上圖所示,操作1和操作2做了重排序卓囚。程序執(zhí)行時(shí)瘾杭,線程A首先寫標(biāo)記變量flag,隨后線程B讀這個(gè)變量哪亿。由于條件判斷為真粥烁,線程B將讀取變量a。此時(shí)蝇棉,變量a還根本沒有被線程A寫入讨阻,在這里多線程程序的語義被重排序破壞了!

※注:本文統(tǒng)一用紅色的虛箭線表示錯(cuò)誤的讀操作篡殷,用綠色的虛箭線表示正確的讀操作钝吮。

下面再讓我們看看,當(dāng)操作3和操作4重排序時(shí)會(huì)產(chǎn)生什么效果(借助這個(gè)重排序,可以順便說明控制依賴性)奇瘦。下面是操作3和操作4重排序后棘催,程序的執(zhí)行時(shí)序圖:

image

在程序中,操作3和操作4存在控制依賴關(guān)系耳标。當(dāng)代碼中存在控制依賴性時(shí)醇坝,會(huì)影響指令序列執(zhí)行的并行度。為此次坡,編譯器和處理器會(huì)采用猜測(Speculation)執(zhí)行來克服控制相關(guān)性對(duì)并行度的影響呼猪。以處理器的猜測執(zhí)行為例,執(zhí)行線程B的處理器可以提前讀取并計(jì)算a*a砸琅,然后把計(jì)算結(jié)果臨時(shí)保存到一個(gè)名為重排序緩沖(reorder buffer ROB)的硬件緩存中宋距。當(dāng)接下來操作3的條件判斷為真時(shí),就把該計(jì)算結(jié)果寫入變量i中明棍。

從圖中我們可以看出,猜測執(zhí)行實(shí)質(zhì)上對(duì)操作3和4做了重排序。重排序在這里破壞了多線程程序的語義嘁傀!

除此之外细办,Java內(nèi)存模型對(duì)volatile和final的語義做了擴(kuò)展。對(duì)volatile語義的擴(kuò)展保證了volatile變量在一些情況下不會(huì)重排序岛啸,volatile的64位變量double和long的讀取和賦值操作都是原子的茴肥。對(duì)final語義的擴(kuò)展保證一個(gè)對(duì)象的構(gòu)建方法結(jié)束前瓤狐,所有final成員變量都必須完成初始化(的前提是沒有this引用溢出)。

Java內(nèi)存模型關(guān)于重排序的規(guī)定嗓节,總結(jié)后如下表所示皆警。

image
image

表中“第二項(xiàng)操作”的含義是指,第一項(xiàng)操作之后的所有指定操作绸罗。如从诲,普通讀不能與其之后的所有volatile寫重排序靡羡。另外略步,JMM也規(guī)定了上述volatile和同步塊的規(guī)則盡適用于存在多線程訪問的情景。例如绽诚,若編譯器(這里的編譯器也包括JIT恩够,下同)證明了一個(gè)volatile變量只能被單線程訪問蜂桶,那么就可能會(huì)把它做為普通變量來處理也切。

留白的單元格代表允許在不違反Java基本語義的情況下重排序雷恃。例如,編譯器不會(huì)對(duì)對(duì)同一內(nèi)存地址的讀和寫操作重排序倒槐,但是允許對(duì)不同地址的讀和寫操作重排序旬痹。

除此之外,為了保證final的新增語義导犹。JSR-133對(duì)于final變量的重排序也做了限制唱凯。

  • 構(gòu)建方法內(nèi)部的final成員變量的存儲(chǔ),并且谎痢,假如final成員變量本身是一個(gè)引用的話磕昼,這個(gè)final成員變量可以引用到的一切存儲(chǔ)操作,都不能與構(gòu)建方法外的將當(dāng)期構(gòu)建對(duì)象賦值于多線程共享變量的存儲(chǔ)操作重排序节猿。

    例如對(duì)于如下語句
    x.finalField = v; ... ;構(gòu)建方法邊界sharedRef = x;

    v.afield = 1; x.finalField = v; ... ; 構(gòu)建方法邊界sharedRef = x;

    這兩條語句中漫雕,構(gòu)建方法邊界前后的指令都不能重排序浸间。

  • 初始讀取共享對(duì)象與初始讀取該共享對(duì)象的final成員變量之間不能重排序吟榴。

    例如對(duì)于如下語句

    x = sharedRef; ... ; i = x.finalField;
    前后兩句語句之間不會(huì)發(fā)生重排序吩翻。

    由于這兩句語句有數(shù)據(jù)依賴關(guān)系,編譯器本身就不會(huì)對(duì)它們重排序细移,但確實(shí)有一些處理器會(huì)對(duì)這種情況重排序弧轧,因此特別制定了這一規(guī)則碗殷。

4.3 內(nèi)存屏障

內(nèi)存屏障(Memory Barrier锌妻,或有時(shí)叫做內(nèi)存柵欄,Memory Fence)是一種CPU指令,用于控制特定條件下的重排序和內(nèi)存可見性問題牍陌。Java編譯器也會(huì)根據(jù)內(nèi)存屏障的規(guī)則禁止重排序员咽。

內(nèi)存屏障可以被分為以下幾種類型

  1. LoadLoad屏障:對(duì)于這樣的語句Load1; LoadLoad; Load2贝室,在Load2及后續(xù)讀取操作要讀取的數(shù)據(jù)被訪問前滑频,保證Load1要讀取的數(shù)據(jù)被讀取完畢。

  2. StoreStore屏障:對(duì)于這樣的語句Store1; StoreStore; Store2银伟,在Store2及后續(xù)寫入操作執(zhí)行前,保證Store1的寫入操作對(duì)其它處理器可見傅物。

  3. LoadStore屏障:對(duì)于這樣的語句Load1; LoadStore; Store2董饰,在Store2及后續(xù)寫入操作被刷出前圆米,保證Load1要讀取的數(shù)據(jù)被讀取完畢榨咐。

  4. StoreLoad屏障:對(duì)于這樣的語句Store1; StoreLoad; Load2,在Load2及后續(xù)所有讀取操作執(zhí)行前齿坷,保證Store1的寫入對(duì)所有處理器可見永淌。它的開銷是四種屏障中最大的遂蛀。 在大多數(shù)處理器的實(shí)現(xiàn)中干厚,這個(gè)屏障是個(gè)萬能屏障,兼具其它三種內(nèi)存屏障的功能所坯。

有的處理器的重排序規(guī)則較嚴(yán)芹助,無需內(nèi)存屏障也能很好的工作状土,Java編譯器會(huì)在這種情況下不放置內(nèi)存屏障伺糠。

為了實(shí)現(xiàn)上一章中討論的JSR-133的規(guī)定退盯,Java編譯器會(huì)這樣使用內(nèi)存屏障泻肯。

image
image

為了保證final字段的特殊語義灶挟,也會(huì)在下面的語句加入內(nèi)存屏障稚铣。

x.finalField = v; StoreStore; sharedRef = x;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末惕医,一起剝皮案震驚了整個(gè)濱河市抬伺,隨后出現(xiàn)的幾起案子峡钓,更是在濱河造成了極大的恐慌若河,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異鲫忍,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)煌寇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鸦泳,“玉大人做鹰,你說我怎么就攤上這事鼎姐。” “怎么了饭尝?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵钥平,是天一觀的道長涉瘾。 經(jīng)常有香客問我,道長负敏,這世上最難降的妖魔是什么其做? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任庶柿,我火速辦了婚禮秽浇,結(jié)果婚禮上柬焕,老公的妹妹穿的比我還像新娘。我一直安慰自己搅轿,他們只是感情好富玷,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布赎懦。 她就那樣靜靜地躺著,像睡著了一般黎茎。 火紅的嫁衣襯著肌膚如雪当悔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天胳挎,我揣著相機(jī)與錄音掸读,去河邊找鬼。 笑死澡罚,一個(gè)胖子當(dāng)著我的面吹牛肾请,可吹牛的內(nèi)容都是我干的铛铁。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼括眠,長吁一口氣:“原來是場噩夢啊……” “哼掷豺!你這毒婦竟也來了薄声?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤德频,失蹤者是張志新(化名)和其女友劉穎壹置,沒想到半個(gè)月后蒸绩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铃肯,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡押逼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年挑格,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雾消。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡立润,死狀恐怖桑腮,靈堂內(nèi)的尸體忽然破棺而出蛉幸,到底是詐尸還是另有隱情,我是刑警寧澤提陶,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布隙笆,位于F島的核電站又固,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏乏冀。R本人自食惡果不足惜辆沦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一识虚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蔚晨,春花似錦、人聲如沸铭腕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至袜瞬,卻和暖如春尝盼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背盾沫。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來泰國打工佩捞, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓禀梳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蚀腿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子畏浆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

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

  • 從三月份找實(shí)習(xí)到現(xiàn)在瞎嬉,面了一些公司,掛了不少便监,但最終還是拿到小米、百度、阿里岩遗、京東、新浪案铺、CVTE红且、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,185評(píng)論 11 349
  • 前言 在 CSDN 的密碼事件爆發(fā)之前壁酬,我都是用的一個(gè)密碼希俩,所以出事之后換了方案一璃搜。 方案一 第一批密碼的規(guī)則是類...
    co233閱讀 1,156評(píng)論 0 0
  • 時(shí)間並沒有消逝 只是移動(dòng)到另一個(gè)地方 誰都沒有離去 只是到另一個(gè)空間遊戲 空氣中的質(zhì)量 早就告訴我們這個(gè)袐密 我們...
    蔡振源閱讀 195評(píng)論 0 1
  • 在將來某天如暖,可能你會(huì)在超市遇見以前的某個(gè)人樱衷,你的目光當(dāng)然會(huì)下意識(shí)地看過去阔籽。在你的記憶中,那個(gè)人可能在你記憶...
    Yech_Matthew閱讀 136評(píng)論 0 0
  • 原文地址前些日子一直在做一些微信公眾號(hào)的H5開發(fā)涣达,在開發(fā)過程中遇到了不少坑度苔。記錄一下: 視頻Android版的視頻...
    趙的拇指閱讀 596評(píng)論 0 0