單例模式的實現(xiàn)方式

單例模式的實現(xiàn)

單例模式的實現(xiàn)一般來說有2種方式:懶漢式(延遲加載)、餓漢式(非延遲加載)。

1. 餓漢式(非延遲加載)

/**
 * Created by liuruijie on 2017/2/13.
 * 餓漢式(非延遲加載)單例類
 */
public class HungrySingleton {
    private static HungrySingleton hungSingleton = new HungrySingleton();

    public static HungrySingleton getInstance() {
        return hungSingleton;
    }

    private HungrySingleton(){}
}

以上代碼,靜態(tài)變量在類被加載的時候初始化,之后就不會再執(zhí)行hungSingleton = new HungrySingleton();語句,所以保證了單例。

還有一種寫法,通過枚舉來實現(xiàn):

/**
 * Created by liuruijie on 2017/2/13.
 * 餓漢式(非延遲加載)單例類 -- 枚舉
 */
public enum  LazySingleton {
    SINGLETON_INSTANCE;

    public static LazySingleton getInstance() {
        return SINGLETON_INSTANCE;
    }
}

就這么簡單。

餓漢式(非延遲加載)這種方式相對簡單,也不會有什么安全問題,但是它的最大弊端顯而易見,就是唯一的實例在這個類被加載時就被創(chuàng)建了,即還未使用實例,資源就已經(jīng)被提前分配了。所以一般來說,為了提高性能,使用更多的還是懶漢式(延遲加載)。

2. 懶漢式(延遲加載)

這個方式水就很深了,它的實現(xiàn)有好幾種,現(xiàn)在由淺入深一個一個看。

1) 最簡單的方式

/**
 * Created by liuruijie on 2017/2/13.
 * 懶漢式(延遲加載)單例類 -- 0
 */
public class LazySingleton {
    private static LazySingleton singleton;
    
    public static LazySingleton getInstance() {
        //獲取實例之前檢查是否為空
        if(singleton == null){ //a
            //第一次獲取的時候總是為空的
            //初始化實例
            singleton = new LazySingleton(); //b
        }
        return singleton;
    }

    private LazySingleton(){}
}

先不說這個寫法的問題,可以看到這個寫法表明了懶漢式(延遲加載)的大概思路。在getInstance()方法種首先檢查唯一的實例是不是還沒被初始化,如果沒有就將其初始化后再返回,已經(jīng)初始化了就直接返回這個實例。

再來說這個寫法的問題,老生常談的線程安全問題。這個寫法完全沒有考慮多線程的情況。

假設有線程1,線程2兩個線程。線程1執(zhí)行了a之后,判斷實例是為空的;之后切換線程2,線程2當然也會執(zhí)行a,并且由于此時實例還未被初始化,所以,線程2會通過判斷,執(zhí)行b,初始化實例;切回線程1,線程1繼續(xù)執(zhí)行b,又將實例初始化了一次,此時對象實例已不唯一,破壞了單例模式。

2) 加鎖的方式

線程并發(fā)出現(xiàn)的問題大多可以用加鎖,也就是同步的方式解決,于是為getInstance方法加上synchronized關鍵字。

/**
 * Created by liuruijie on 2017/2/13.
 * 懶漢式(延遲加載)單例類 -- 1
 */
public class LazySingleton {
    private static LazySingleton singleton;

    public static synchronized LazySingleton getInstance() {
        //獲取實例之前檢查是否為空
        if(singleton == null){ //a
            //第一次獲取的時候總是為空的
            //初始化實例
            singleton = new LazySingleton(); //b
        }
        return singleton; //c
    }

    private LazySingleton(){}
}

此時問題是解決了,但是我們都知道加鎖同步會對性能產(chǎn)生很大影響,我們應該讓在同步塊中的語句盡量少。現(xiàn)在來分析一下可以優(yōu)化的地方,這個方法也就3條語句,a、b、c。從第一種方式種的問題可以看出,主要成因與a和b有關,于是我們應該縮小同步塊范圍到這兩條語句。

/**
 * Created by liuruijie on 2017/2/13.
 * 懶漢式(延遲加載)單例類 -- 1
 */
public class LazySingleton {
    private static LazySingleton singleton;

    public static LazySingleton getInstance() {
        synchronized(LazySingleton.class){
            //獲取實例之前檢查是否為空
            if(singleton == null){ //a
                //第一次獲取的時候總是為空的
                //初始化實例
                singleton = new LazySingleton(); //b
            }
        }
        return singleton; //c
    }

    private LazySingleton(){}
}

這樣看似很美妙,解決了線程安全問題,還優(yōu)化了性能。但是,現(xiàn)在還沒有優(yōu)化徹底。想想看,只有第一次,對象實例還沒有初始化的時候,鎖才有意義,實例初始化之后,不會再執(zhí)行語句b了,但是還是要經(jīng)過synchronized,這是無意義的,所以還能優(yōu)化。

3)雙重檢測鎖的方式

/**
 * Created by liuruijie on 2017/2/13.
 * 懶漢式(延遲加載)單例類 -- 2
 */
public class LazySingleton {
    private static volatile LazySingleton singleton;

    public static LazySingleton getInstance() {
        if (singleton==null){ //a
            synchronized(LazySingleton.class){ //b
                //獲取實例之前檢查是否為空
                if(singleton == null){ //c
                    //第一次獲取的時候總是為空的
                    //初始化實例
                    singleton = new LazySingleton(); //d
                }
            }
        }
        return singleton; //e
    }

    private LazySingleton(){}
}

什么叫雙重檢測鎖(Double Checked Locking,DCL),這么高大上的名字,但它其實就是兩個if判斷語句加一個synchronized鎖 -_=。

由于我們希望在初始化實例之后不要經(jīng)過無意義的同步語句。所以往外面再加一個沒有同步的if條件去判斷實例是否為空。但是當然也必須保留原來在同步塊里面的if語句,因為初衷就是對初始化對象時的判斷語句和賦值語句做同步處理。

這樣第一次,線程1執(zhí)行到c后,切換線程2,線程2執(zhí)行到b會阻塞,線程1執(zhí)行完同步塊中的d后,切換線程2,線程2執(zhí)行c時,由于實例已經(jīng)初始化,所以不會去執(zhí)行d,而直接執(zhí)行e返回了。并且對象實例初始化之后,每次調(diào)用getInstance方法都會在a執(zhí)行后執(zhí)行調(diào)到e返回,不會再經(jīng)過synchronized了。

眼睛犀利的朋友可能看到了還有一個地方的不同,那就是靜態(tài)變量singleton前面多了個volatile關鍵字去修飾。這是為了解決雙重檢測鎖存在的線程安全問題。

很多人覺得,這樣寫非常完美,怎么也看不出問題。但是如果不加volatile的確是有問題的,因為java虛擬機會進行指令重排

volatile關鍵字

先來說說volatile關鍵字,它主要有兩個作用,他能夠保證變量的可見性,并且他能夠防止指令重排序,這里主要用到它的第二個作用。

指令重排

什么是指令重排序,結合以上代碼

singleton = new LazySingleton();

這只是一條語句,看上去只進行變量初始化一個簡單的操作,但是在java虛擬機層面,它是很復雜的,分為很多個操作,需要進行類加載檢查,分配內(nèi)存,初始化對象頭信息等等。不過解釋這里的問題,只需要將其大致分為三個部分:
(1)分配內(nèi)存
(2)調(diào)用構造函數(shù)
(3)賦值
這只是我們所覺得的正常的指令順序,但是java虛擬機在編譯時這些指令很可能變成:
(1)分配內(nèi)存
(2)賦值
(3)調(diào)用構造函數(shù)
因為對于單線程來說,這兩個指令順序并沒有什么區(qū)別,因為賦值和調(diào)用構造函數(shù)是沒有先后關系的,我可以先將對象內(nèi)存地址賦值給引用然后再去調(diào)用構造函數(shù)初始化對象的屬性,這樣得到的結果是一樣的。而且單線程的所有語句都是串行的,也就是順序執(zhí)行的,能夠保證在下一條語句執(zhí)行的時候,這三個指令都已執(zhí)行完成。

不過一旦到了多線程的環(huán)境中,就存在潛在問題,現(xiàn)在回到代碼。

在指令重排之后,當線程1執(zhí)行了(1)和(2)還未執(zhí)行(3)的時候,就切到線程2執(zhí)行,此時線程2在第一個if判斷時,對象雖然還不完整,但已經(jīng)不為空了,所以線程2會跳到return語句,直接返回一個不完整的對象,這樣只要線程1還沒有執(zhí)行完初始化操作中的第三條指令,你的程序就會繼續(xù)使用一個不完整的對象,這樣產(chǎn)生的后果肯定是不堪設想的。

而volatile關鍵字會杜絕關于被修飾變量的指令重排的發(fā)生,也就是說始終保持正常的指令順序,這樣保證只要語句singleton = new LazySingleton()沒有執(zhí)行完,singleton變量永遠為空。

在加上了volatile關鍵字后,DCL就能正常工作。

4)ThreadLocal方式

除了加volatile關鍵字外,要解決DCL的問題,還有一種方式,就是使用ThreadLocal

/**
 * Created by liuruijie on 2017/2/13.
 * 懶漢式(延遲加載)單例類 -- 3
 */
public class LazySingleton {
    private static ThreadLocal<LazySingleton> threadLocal = new ThreadLocal<>();
    private static LazySingleton singleton;

    public static LazySingleton getInstance() {
        if (threadLocal.get()==null) { //a
            synchronized (LazySingleton.class) { //b
                //獲取實例之前檢查是否為空
                if (singleton == null) { //c
                    //第一次獲取的時候總是為空的
                    //初始化實例
                    singleton = new LazySingleton(); //d
                }
            }
            threadLocal.set(singleton); //e
        }
        return singleton; //f
    }

    private LazySingleton(){}
}

什么是ThreadLocal變量,就是只屬于當前線程的局部變量,換句話說就是每個線程都會持有一個該threadlocal變量的副本,當不同的線程訪問同一個ThreadLocal變量,得到的值可能是不同的,它有兩個重要的方法,一個是get,一個是set,對應

怎么用它來解決DCL的問題呢,思路是這樣的:
在volatile方式中,已經(jīng)分析了問題所在,就是第一層if條件判斷時可能會出現(xiàn)不完整同時又不為空的對象實例,于是,將這里的判斷條件替換為只屬于當前線程的局部變量,因為這個局部變量一開始是為空的,所以無論線程1是否執(zhí)行完語句d,線程2,線程3,的threadlocal變量都是為空的,第一個if判斷條件都會通過,接著就是同步塊了,等待線程1執(zhí)行完語句d,也就是對象實例初始化完成之后,第二層的if判斷條件不會滿足,接著各個線程分別執(zhí)行了threadloacal.set,也就是語句e后,其threadlocal變量就不為空了,之后便不會通過第一層的if條件,跳到語句f返回實例。

threadlocal避開了DCL的問題,但是卻增大了內(nèi)存開銷,因為threadlocal本質(zhì)上是用一個hashmap來管理的這些變量,鍵為線程對象,值為該線程對應的局部變量副本的值。

5)內(nèi)部類方式

除了使用DCL之外,延遲加載的單例模式還可以通過內(nèi)部類來實現(xiàn)。

/**
 * Created by liuruijie on 2017/2/13.
 * 懶漢式(延遲加載)單例類 -- 3
 */
public class LazySingleton {

    public static LazySingleton getInstance() {
        //直接返回內(nèi)部類中的實例對象
        return LazyHolder.lazySingleton;
    }

    private LazySingleton(){}

    //靜態(tài)內(nèi)部類
    private static class LazyHolder{
        //持有外部類的實例,并初始化
        private static LazySingleton lazySingleton = new LazySingleton();
    }
}

主要思路是,內(nèi)部類持有外部類的靜態(tài)實例,并將其在內(nèi)部類被加載時就初始化。然后在外部類中的getInstance方法中返回此實例。類似餓漢式,由于內(nèi)部類只會被加載一次,也就是只會執(zhí)行一次初始化語句,所以保證了實例的唯一。

而內(nèi)部類什么時候加載,其實所有的類都是在使用它的時候被加載的,包括內(nèi)部類。所以內(nèi)部類不會隨著外部類的加載而加載,只有在使用它的時候才會被加載。

使用一個類情況有哪些?
1.調(diào)用靜態(tài)變量
2.調(diào)用靜態(tài)方法
3.創(chuàng)建此類對象

做一個簡單的實驗就明白了。

/**
 * Created by liuruijie on 2017/2/14.
 * 內(nèi)部類與外部類加載時機
 */
public class Out {
    static {
        System.out.println("外部類被加載");
    }

    public static void loadOut(){
        //這個方法不使用內(nèi)部類
    }

    public static void loadIn(){
        int a = In.num; //通過調(diào)用靜態(tài)變量來使用內(nèi)部類
    }

    private static class In{
        static int num;
        static {
            System.out.println("內(nèi)部類被加載");
        }
    }
}

用到的是只要一加載類就會執(zhí)行的靜態(tài)代碼塊來驗證。
首先只加載外部類

Out.loadOut();

運行結果:

圖片.png

可以看到只有外部類被加載了,內(nèi)部類并沒有被加載

加載內(nèi)部類

Out.loadIn();

結果:

圖片.png

結果證明了之前的結論。

其中需要注意的有兩點

(1)這里的內(nèi)部類必須是靜態(tài)內(nèi)部類,原因很簡單,這里的單例模式獲取對象實例需要用到內(nèi)部類,而非靜態(tài)內(nèi)部類同非靜態(tài)變量一樣是對象級的,必須先有對象實例才能訪問,這樣就產(chǎn)生了矛盾。
并且只有靜態(tài)內(nèi)部類才能夠持有靜態(tài)變量和方法。至于為什么,目前我還沒找出好的解釋,所以就把它當成一個語法規(guī)定吧。

(2)內(nèi)部類的訪問修飾符應該為private或者protected,因為靜態(tài)內(nèi)部類在其他類中是可以被訪問的,這個雖然不影響單例模式,但是類應該盡量將具體的實現(xiàn)屏蔽起來,這樣外部就不會知道這個單例類的實現(xiàn)是采用的內(nèi)部類的方式了。(個人看法)

小結

對于單例模式,為了提高性能而通常選擇懶漢式的實現(xiàn),但是又帶來了許多線程安全問題,能解決這些問題的有3種實現(xiàn),帶volatile的DCL、用threadlocal的DCL、靜態(tài)內(nèi)部類。其中最簡單的是靜態(tài)內(nèi)部類的方式,也最容易理解。但是另外兩個方式對于多線程的學習和理解來講也是很重要的。

單例注冊表

上面提到的單例模式雖好,但是都有一點瑕疵,就是不能重用。如果我要將一個類變成單例的,我必須要在這個類上寫上面提到的那些代碼,過段時間,另一個類也需要寫成單例的,我又要寫上這些代碼。

單例注冊表就是一種可以獲得任何類的唯一實例的一個表。只要是同一個單例注冊表獲取到的同一個類的實例,總是相同的。

先看看使用起來是怎么樣的,隨便建一個類用來測試:

/**
 * Created by liuruijie on 2017/2/13.
 * 隨便建的一個類
 */
public class Student {
 //假裝有很多屬性和方法 
}

獲取這個類的單例:

        //這是一個單例注冊表
        BeanFactory beanFactory = BeanFactory.getInstance();
        
        //獲取實例1
        Student student1 = (Student) beanFactory
                .getBean(Student.class.getName());
        //獲取實例2
        Student student2 = (Student) beanFactory
                .getBean(Student.class.getName());

        //比較獲取到的兩個實例
        System.out.println(student1.hashCode());
        System.out.println(student2.hashCode());

看看結果:


運行結果.png

兩個對象是一樣的。
其實這個單例注冊表的實現(xiàn)很簡單,就是用一個hashmap來維護單例對象。
代碼一看便知:

/**
 * Created by liuruijie on 2017/2/13.
 * 單例注冊表
 */
public class BeanFactory {
    /**
     * 這些是維護此注冊表的,
     * 因為不是重點
     * 所以采用了最簡單的方式
     * 可以用其他方式
     */
    private static BeanFactory beanFactory = new BeanFactory();
    private BeanFactory(){
    }
    public static BeanFactory getInstance() {
        return beanFactory;
    }

    //緩存單例對象的hash表
    private final HashMap<String, Object> cacheMap = new HashMap<>();

    //通過類名獲取其單例對象
    public Object getBean(String className) {
        Object bean = cacheMap.get(className);
        //使用雙重檢測鎖來實現(xiàn)單例
        if (bean == null) {
            synchronized (this.cacheMap) {
                //第二次檢測
                bean = cacheMap.get(className);
                if (bean == null) {
                    try {
                        bean = Class.forName(className).newInstance();
                    } catch (InstantiationException e) {
                        System.err.println("could not instance an object of type:" + className);
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        System.err.println("could not access class " + className);
                        e.printStackTrace();
                    } catch (ClassNotFoundException e) {
                        System.err.println("could not find class " + className);
                        e.printStackTrace();
                    }
                }
                cacheMap.put(className, bean);
            }

        }
        return bean;
    }
}

注意這里的cacheMap是沒有加volatile關鍵字的,為什么,因為在bean = Class.forName(className).newInstance();這句沒執(zhí)行完的時候,cacheMap中不可能有不完整的對象,只有在后面的cacheMap.put(className, bean);執(zhí)行之后,cacheMap中才會有對應的對象并且肯定是完整的。所以這里不需要加volatile。

可能會有人覺得,這些類名和方法名很熟悉,讓人聯(lián)想到spring框架,我想說的是spring就是采用這種方式來維護bean的單例性的。當然,要做好這樣一個類,上面那些肯定是不夠的。
看看spring這段源碼:

public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
···
    protected <T> T doGetBean(String name, Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException {
        final String beanName = this.transformedBeanName(name);
        Object sharedInstance = this.getSingleton(beanName);
        Object bean;
        if(sharedInstance != null && args == null) {
···
            bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, (RootBeanDefinition)null);
        }else{
        final RootBeanDefinition ex1 = this.getMergedLocalBeanDefinition(beanName);
···
        if(ex1.isSingleton()) {
            sharedInstance = this.getSingleton(beanName, new ObjectFactory() {
                public Object getObject() throws BeansException {
                    try {
                    return AbstractBeanFactory.this.createBean(beanName, ex1, args);
                    } catch (BeansException var2) {
                        AbstractBeanFactory.this.destroySingleton(beanName);
                        throw var2;
                    }
                }
            });
            bean = this.getObjectForBeanInstance(sharedInstanc, name, beanName, ex1);
       }
···
      return bean;
      }
}

spring這段代碼涉及到的東西很多,而且將許多語句封裝成了方法,不過不用仔細看,從這幾句就知道單例注冊表在其中是有應用的。

最后的總結

本篇文章是我學習單例模式的筆記整理出來的,從單例模式的實現(xiàn)到單例模式的應用都有所涉及,其中還有許多地方可以深究,比如,延遲加載的各種并發(fā)問題,volatile關鍵字所涉及到的java的內(nèi)存模型,還有spring的單例模式具體實現(xiàn)等等。

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

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

  • PS:這是一個小白的學習記錄之路。大神看見不要笑,獅虎看見不要生氣的哈。 題目:單例模式的實現(xiàn)方式 解決思路:獅虎...
    福小滿滿滿閱讀 350評論 0 0
  • 單例模式(SingletonPattern)一般被認為是最簡單、最易理解的設計模式,也因為它的簡潔易懂,是項目中最...
    成熱了閱讀 4,280評論 4 34
  • 最近看到組里有人實現(xiàn)單例模式,采用靜態(tài)內(nèi)部類的方式,不是很懂這種寫法的優(yōu)點,查了一下各種寫法的優(yōu)缺點,總結一下。內(nèi)...
    hello_cc閱讀 272評論 0 0
  • 從三月份找實習到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍閱讀 42,319評論 11 349
  • 曾經(jīng)滄海難為水,除卻巫山不是云。當浮傷年華,有那樣一個人經(jīng)過,從此,任憑花開花落在這煙火塵世間,看多少月夕花朝,也...
    90度分享閱讀 213評論 0 0