<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java延遲加載的最佳實(shí)踐應(yīng)用示例!

          共 2240字,需瀏覽 5分鐘

           ·

          2021-02-19 14:25

          作者 |?S.L

          來源 |?http://r6d.cn/abGzy

          代碼中的很多操作都是Eager的,比如在發(fā)生方法調(diào)用的時(shí)候,參數(shù)會(huì)立即被求值。總體而言,使用Eager方式讓編碼本身更加簡單,然而使用Lazy的方式通常而言,即意味著更好的效率。

          延遲初始化

          一般有幾種延遲初始化的場(chǎng)景:

          • 對(duì)于會(huì)消耗較多資源的對(duì)象:這不僅能夠節(jié)省一些資源,同時(shí)也能夠加快對(duì)象的創(chuàng)建速度,從而從整體上提升性能。
          • 某些數(shù)據(jù)在啟動(dòng)時(shí)無法獲取:比如一些上下文信息可能在其他攔截器或處理中才能被設(shè)置,導(dǎo)致當(dāng)前bean在加載的時(shí)候可能獲取不到對(duì)應(yīng)的變量的值,使用 延遲初始化可以在真正調(diào)用的時(shí)候去獲取,通過延遲來保證數(shù)據(jù)的有效性。

          在Java8中引入的lambda對(duì)于我們實(shí)現(xiàn)延遲操作提供很大的便捷性,如Stream、Supplier等,下面介紹幾個(gè)例子。

          Lambda

          Supplier

          通過調(diào)用get()方法來實(shí)現(xiàn)具體對(duì)象的計(jì)算和生成并返回,而不是在定義Supplier的時(shí)候計(jì)算,從而達(dá)到了延遲初始化的目的。但是在使用 中往往需要考慮并發(fā)的問題,即防止多次被實(shí)例化,就像Spring的@Lazy注解一樣。

          public?class?Holder?{
          ????//?默認(rèn)第一次調(diào)用heavy.get()時(shí)觸發(fā)的同步方法
          ????private?Supplier?heavy?=?()?->?createAndCacheHeavy();?
          ????public?Holder()?{
          ????????System.out.println("Holder?created");
          ????}
          ????public?Heavy?getHeavy()?{
          ????????//?第一次調(diào)用后heavy已經(jīng)指向了新的instance,所以后續(xù)不再執(zhí)行synchronized
          ????????return?heavy.get();?
          ????}
          ????//...

          ????private?synchronized?Heavy?createAndCacheHeavy()?{
          ????????//?方法內(nèi)定義class,注意和類內(nèi)的嵌套class在加載時(shí)的區(qū)別
          ????????class?HeavyFactory?implements?Supplier<Heavy>?{
          ????????????//?饑渴初始化
          ????????????private?final?Heavy?heavyInstance?=?new?Heavy();?
          ????????????public?Heavy?get()?{
          ????????????????//?每次返回固定的值
          ????????????????return?heavyInstance;?
          ????????????}?
          ????????}
          ????????
          ????????//第一次調(diào)用方法來會(huì)將heavy重定向到新的Supplier實(shí)例
          ????????if(!HeavyFactory.class.isInstance(heavy))?{
          ????????????heavy?=?new?HeavyFactory();
          ????????}
          ????????return?heavy.get();
          ????}
          }

          當(dāng)Holder的實(shí)例被創(chuàng)建時(shí),其中的Heavy實(shí)例還沒有被創(chuàng)建。下面我們假設(shè)有三個(gè)線程會(huì)調(diào)用getHeavy方法,其中前兩個(gè)線程會(huì)同時(shí)調(diào)用,而第三個(gè)線程會(huì)在稍晚的時(shí)候調(diào)用。

          當(dāng)前兩個(gè)線程調(diào)用該方法的時(shí)候,都會(huì)調(diào)用到createAndCacheHeavy方法,由于這個(gè)方法是同步的。因此第一個(gè)線程進(jìn)入方法體,第二個(gè)線程開始等待。在方法體中會(huì)首先判斷當(dāng)前的heavy是否是HeavyInstance的一個(gè)實(shí)例。如果不是,就會(huì)將heavy對(duì)象替換成HeavyFactory類型的實(shí)例。顯然,第一個(gè)線程執(zhí)行判斷的時(shí)候,heavy對(duì)象還只是一個(gè)Supplier的實(shí)例,所以heavy會(huì)被替換成為HeavyFactory的實(shí)例,此時(shí)heavy實(shí)例會(huì)被真正的實(shí)例化。等到第二個(gè)線程進(jìn)入執(zhí)行該方法時(shí),heavy已經(jīng)是HeavyFactory的一個(gè)實(shí)例了,所以會(huì)立即返回(即heavyInstance)。當(dāng)?shù)谌齻€(gè)線程執(zhí)行g(shù)etHeavy方法時(shí),由于此時(shí)的heavy對(duì)象已經(jīng)是HeavyFactory的實(shí)例了,因此它會(huì)直接返回需要的實(shí)例(即heavyInstance),和同步方法createAndCacheHeavy沒有任何關(guān)系了。

          以上代碼實(shí)際上實(shí)現(xiàn)了一個(gè)輕量級(jí)的虛擬代理模式(Virtual Proxy Pattern)。保證了懶加載在各種環(huán)境下的正確性。

          還有一種基于delegate的實(shí)現(xiàn)方式更好理解一些(github):

          import?java.util.concurrent.ConcurrentHashMap;
          import?java.util.concurrent.ConcurrentMap;
          import?java.util.function.Supplier;

          public?class?MemoizeSupplier<T>?implements?Supplier<T>?{

          ?final?Supplier?delegate;
          ?ConcurrentMap,?T>?map?=?new?ConcurrentHashMap<>(1);

          ?public?MemoizeSupplier(Supplier?delegate)?{
          ??this.delegate?=?delegate;
          ?}

          ?@Override
          ?public?T?get()?{
          ?????//?利用computeIfAbsent方法的特性,保證只會(huì)在key不存在的時(shí)候調(diào)用一次實(shí)例化方法,進(jìn)而實(shí)現(xiàn)單例
          ??return?this.map.computeIfAbsent(MemoizeSupplier.class,
          ????k?->?this.delegate.get())
          ;
          ?}

          ?public?static??Supplier?of(Supplier?provider)?{
          ??return?new?MemoizeSupplier<>(provider);
          ?}
          }

          以及一個(gè)更復(fù)雜但功能更多的CloseableSupplier:

          public?static?class?CloseableSupplier<T>?implements?Supplier<T>,?Serializable?{

          ????????private?static?final?long?serialVersionUID?=?0L;
          ????????private?final?Supplier?delegate;
          ????????private?final?boolean?resetAfterClose;
          ????????private?volatile?transient?boolean?initialized;
          ????????private?transient?T?value;

          ????????private?CloseableSupplier(Supplier?delegate,?boolean?resetAfterClose)?{
          ????????????this.delegate?=?delegate;
          ????????????this.resetAfterClose?=?resetAfterClose;
          ????????}

          ????????public?T?get()?{
          ????????????//?經(jīng)典Singleton實(shí)現(xiàn)
          ????????????if?(!(this.initialized))?{?//?注意是volatile修飾的,保證happens-before,t一定實(shí)例化完全
          ????????????????synchronized?(this)?{
          ????????????????????if?(!(this.initialized))?{?//?Double?Lock?Check
          ????????????????????????T?t?=?this.delegate.get();
          ????????????????????????this.value?=?t;
          ????????????????????????this.initialized?=?true;
          ????????????????????????return?t;
          ????????????????????}
          ????????????????}
          ????????????}
          ????????????//?初始化后就直接讀取值,不再同步搶鎖
          ????????????return?this.value;
          ????????}

          ????????public?boolean?isInitialized()?{
          ????????????return?initialized;
          ????????}

          ????????public??void?ifPresent(ThrowableConsumer?consumer)?throws?X?{
          ????????????synchronized?(this)?{
          ????????????????if?(initialized?&&?this.value?!=?null)?{
          ????????????????????consumer.accept(this.value);
          ????????????????}
          ????????????}
          ????????}

          ????????public??Optional?map(Functionsuper?T,???extends?U>?mapper)?{
          ????????????checkNotNull(mapper);
          ????????????synchronized?(this)?{
          ????????????????if?(initialized?&&?this.value?!=?null)?{
          ????????????????????return?ofNullable(mapper.apply(value));
          ????????????????}?else?{
          ????????????????????return?empty();
          ????????????????}
          ????????????}
          ????????}

          ????????public?void?tryClose()?{
          ????????????tryClose(i?->?{?});
          ????????}

          ????????public??void?tryClose(ThrowableConsumer?close)?throws?X?{
          ????????????synchronized?(this)?{
          ????????????????if?(initialized)?{
          ????????????????????close.accept(value);
          ????????????????????if?(resetAfterClose)?{
          ????????????????????????this.value?=?null;
          ????????????????????????initialized?=?false;
          ????????????????????}
          ????????????????}
          ????????????}
          ????????}

          ????????public?String?toString()?{
          ????????????if?(initialized)?{
          ????????????????return?"MoreSuppliers.lazy("?+?get()?+?")";
          ????????????}?else?{
          ????????????????return?"MoreSuppliers.lazy("?+?this.delegate?+?")";
          ????????????}
          ????????}
          ????}

          Stream

          Stream中的各種方法分為兩類:

          • 中間方法(limit()/iterate()/filter()/map())
          • 結(jié)束方法(collect()/findFirst()/findAny()/count())

          前者的調(diào)用并不會(huì)立即執(zhí)行,只有結(jié)束方法被調(diào)用后才會(huì)依次從前往后觸發(fā)整個(gè)調(diào)用鏈條。但是需要注意,對(duì)于集合來說,是每一個(gè)元素依次按照處理鏈條執(zhí)行到尾,而不是每一個(gè)中間方法都將所有能處理的元素全部處理一遍才觸發(fā) 下一個(gè)中間方法。比如:

          List?names?=?Arrays.asList("Brad",?"Kate",?"Kim",?"Jack",?"Joe",?"Mike");

          final?String?firstNameWith3Letters?=?names.stream()
          ????.filter(name?->?length(name)?==?3)
          ????.map(name?->?toUpper(name))
          ????.findFirst()
          ????.get();

          System.out.println(firstNameWith3Letters);

          當(dāng)觸發(fā)findFirst()這一結(jié)束方法的時(shí)候才會(huì)觸發(fā)整個(gè)Stream鏈條,每個(gè)元素依次經(jīng)過filter()->map()->findFirst()后返回。所以filter()先處理第一個(gè)和第二個(gè)后不符合條件,繼續(xù)處理第三個(gè)符合條件,再觸發(fā)map()方法,最后將轉(zhuǎn)換的結(jié)果返回給findFirst()。所以filter()觸發(fā)了3次,map()觸發(fā)了1次。

          好,讓我們來看一個(gè)實(shí)際問題,關(guān)于無限集合。

          Stream類型的一個(gè)特點(diǎn)是:它們可以是無限的。這一點(diǎn)和集合類型不一樣,在Java中的集合類型必須是有限的。Stream之所以可以是無限的也是源于Stream「懶」的這一特點(diǎn)。

          Stream只會(huì)返回你需要的元素,而不會(huì)一次性地將整個(gè)無限集合返回給你。

          Stream接口中有一個(gè)靜態(tài)方法iterate(),這個(gè)方法能夠?yàn)槟銊?chuàng)建一個(gè)無限的Stream對(duì)象。它需要接受兩個(gè)參數(shù):

          public static Stream iterate(final T seed, final UnaryOperator f)

          其中,seed表示的是這個(gè)無限序列的起點(diǎn),而UnaryOperator則表示的是如何根據(jù)前一個(gè)元素來得到下一個(gè)元素,比如序列中的第二個(gè)元素可以這樣決定:f.apply(seed)。

          下面是一個(gè)計(jì)算從某個(gè)數(shù)字開始并依次返回后面count個(gè)素?cái)?shù)的例子:

          public?class?Primes?{
          ????
          ????public?static?boolean?isPrime(final?int?number)?{
          ????????return?number?>?1?&&
          ????????????//?依次從2到number的平方根判斷number是否可以整除該值,即divisor
          ????????????IntStream.rangeClosed(2,?(int)?Math.sqrt(number))
          ????????????????.noneMatch(divisor?->?number?%?divisor?==?0);
          ????}
          ????
          ????private?static?int?primeAfter(final?int?number)?{
          ????????if(isPrime(number?+?1))?//?如果當(dāng)前的數(shù)的下一個(gè)數(shù)是素?cái)?shù),則直接返回該值
          ????????????return?number?+?1;
          ????????else?//?否則繼續(xù)從下一個(gè)數(shù)據(jù)的后面繼續(xù)找到第一個(gè)素?cái)?shù)返回,遞歸
          ????????????return?primeAfter(number?+?1);
          ????}
          ????public?static?List?primes(final?int?fromNumber,?final?int?count)?{
          ????????return?Stream.iterate(primeAfter(fromNumber?-?1),?Primes::primeAfter)
          ????????????.limit(count)
          ????????????.collect(Collectors.toList());
          ????}
          ????//...
          }

          對(duì)于iterate和limit,它們只是中間操作,得到的對(duì)象仍然是Stream類型。對(duì)于collect方法,它是一個(gè)結(jié)束操作,會(huì)觸發(fā)中間操作來得到需要的結(jié)果。

          如果用非Stream的方式需要面臨兩個(gè)問題:

          • 一是無法提前知曉fromNumber后count個(gè)素?cái)?shù)的數(shù)值邊界是什么
          • 二是無法使用有限的集合來表示計(jì)算范圍,無法計(jì)算超大的數(shù)值

          即不知道第一個(gè)素?cái)?shù)的位置在哪兒,需要提前計(jì)算出來第一個(gè)素?cái)?shù),然后用while來處理count次查找后續(xù)的素?cái)?shù)。可能primes方法的實(shí)現(xiàn)會(huì)拆成兩部分,實(shí)現(xiàn)復(fù)雜。如果用Stream來實(shí)現(xiàn),流式的處理,無限迭代,指定截止條件,內(nèi)部的一套機(jī)制可以保證實(shí)現(xiàn)和執(zhí)行都很優(yōu)雅。

          喜歡本文的朋友,歡迎點(diǎn)擊下方卡片
          關(guān)注我,訂閱更多精彩內(nèi)容


          往期推薦

          不容錯(cuò)過的灰度發(fā)布系統(tǒng)架構(gòu)設(shè)計(jì)

          還在封裝各種 Util 工具類?這個(gè)神級(jí)框架幫你解決所有問題!

          阿里開源臺(tái)柱 Ant Design 源碼倉庫被刪了...

          GitHub最最最火的開源爬蟲工具箱,一爬就取

          明天即將開工,把今年的Flag加到頭像上,時(shí)刻鞭策自己吧!

          情人節(jié)微信紅包數(shù)據(jù)公布,你離海王與海后有多遠(yuǎn)...



          瀏覽 55
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    日韩在线观看一区 | 国产精品国产亚洲精品看不 | 久久精品三级视频 | 免费的操逼网站 | 久久久久久无码麻豆 |