最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于ThreadLocal 的用法及內(nèi)存泄露(內(nèi)存溢出)

 更新時(shí)間:2021年10月11日 16:43:20   作者:知我飯否  
這篇文章主要介紹了基于ThreadLocal 的用法及內(nèi)存泄露(內(nèi)存溢出),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

ThreadLocal 看名字 就可以看出一點(diǎn)頭緒來(lái),線(xiàn)程本地。

來(lái)看一下java對(duì)他的描述:

該類(lèi)提供線(xiàn)程本地變量。這些變量與它們的正常對(duì)應(yīng)變量的不同之處在于,每個(gè)線(xiàn)程(通過(guò)ThreadLocal的 get 或 set方法)訪(fǎng)問(wèn)自己的、獨(dú)立初始化的變量副本。 ThreadLocal實(shí)例通常是類(lèi)中的私有靜態(tài)字段。

上面這段話(huà)呢,一個(gè)重點(diǎn)就是 每個(gè)線(xiàn)程都有自己的專(zhuān)屬變量,這個(gè)專(zhuān)屬變量呢,是不會(huì)被其他線(xiàn)程影響的。

使用

public class ThreadLocalTwo {
    //靜態(tài)的 延長(zhǎng)生命周期。final  不可改變
    private static final ThreadLocal<Integer> threalLocal = ThreadLocal.withInitial(() -> {
        return 0;
    });
    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                //取出來(lái)
                int inner = threalLocal.get();
                //使用
                System.out.println(Thread.currentThread().getName() + "   " + inner);
                LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
                //更新值存入
                threalLocal.set(++inner);
            }
        }, "three").start();
        new Thread(() -> {
            while (true) {
                //取出來(lái)
                int inner = threalLocal.get();
                //使用
                System.out.println(Thread.currentThread().getName() + "   " + inner);
                LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
                //更新值存入
                threalLocal.set(++inner);
            }
        }, "four").start();
    }
}

使用這個(gè)我只是隨便寫(xiě)一個(gè)demo,具體的邏輯有很多種,只要你想,就會(huì)有很多種寫(xiě)法。具體看業(yè)務(wù)需求。

個(gè)人理解

ThreadLocal 類(lèi)似于一個(gè)工具,通過(guò)這個(gè)工具,來(lái)為當(dāng)前線(xiàn)程設(shè)定修改移除本地副本。,如果 你查看Thread的源碼會(huì)發(fā)現(xiàn)下面這段代碼

    /* ThreadLocal values pertaining to this thread. This map is maintained
     by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

這是靜態(tài)內(nèi)部類(lèi)構(gòu)造的一個(gè)字段,那么我們看一下 ThreadLocal.ThreadLocalMap的源碼.

     static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        
        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

上面代碼我們可以發(fā)現(xiàn) ThreadLocal.ThreadLocalMap這個(gè)內(nèi)部靜態(tài)類(lèi),里面還包含這一個(gè)內(nèi)部靜態(tài)類(lèi)Entry。

這個(gè)Entry 繼承了WeakReference,并且將ThreadLocal作為弱引用類(lèi)型。這表明 ThreadLocal如果沒(méi)有其他的強(qiáng)引用時(shí)候,說(shuō)不定 有可能不知道啥時(shí)候就被回收了。

那么至于 value呢? 我可以肯定的告訴你 value不會(huì)被回收,即便 傳進(jìn)來(lái)的v是個(gè)匿名類(lèi)。

value持有著線(xiàn)程的本地副本的引用

Entry[] table 這個(gè)持有 entry的引用

現(xiàn)在 ,只需要知道

1 弱引用對(duì)象,會(huì)持有引用對(duì)象的引用,弱引用對(duì)象并不能決定 引用對(duì)象是否回收。

2 弱引用的子類(lèi)的 如果有自己的字段的話(huà), 那么那個(gè)字段是強(qiáng)引用,不會(huì)被回收

3 弱引用對(duì)象,如果是new出來(lái)的,那么弱引用對(duì)象本身也是一個(gè)強(qiáng)引用。弱引用對(duì)象自己不會(huì)被回收。

構(gòu)造方法

一個(gè)默認(rèn)的無(wú)參構(gòu)造方法 ,沒(méi)啥好講的,,

public ThreadLocal() {
    }

使用

  private static final ThreadLocal<String> construct  = new ThreadLocal<>(){
        //如果 不重寫(xiě)這個(gè)方法的話(huà),默認(rèn)返回null
        @Override
        protected String initialValue() {
            return "默認(rèn)值";
        }
    };

靜態(tài)方法

note Java8新增的方法

 public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }

上面的這個(gè)靜態(tài)方法呢,生成一個(gè)ThreadLocal對(duì)象,參數(shù)是一個(gè)Supplier函數(shù)接口。

下面展示一個(gè)代碼

private static final ThreadLocal<String> local = ThreadLocal.withInitial(() -> "默認(rèn)值");

上面這段代碼使用了Lambda表達(dá)式, 比起上面 new 并且重寫(xiě)方法的寫(xiě)法,代碼會(huì)少很多,顯得很有逼格對(duì)不。

如果你對(duì)java8的Lambda不清楚的話(huà),可以看這篇文章:java Lambda表達(dá)式的使用

公共方法

//返回當(dāng)前線(xiàn)程本地副本的值。如果本地副本為null,則返回初始化為調(diào)用{@link #initialValue}方法返回的值。
public T get()
//將當(dāng)前線(xiàn)程的本地副本 設(shè)為 value
public void set(T value)
//將當(dāng)前線(xiàn)程的本地副本移除,如果后面調(diào)用get()方法的話(huà),會(huì)返回T initialValue()的值
public void remove()

內(nèi)存泄露

接下來(lái)講一下,ThreadLocal配合線(xiàn)程池時(shí)候 會(huì)出現(xiàn)內(nèi)存泄漏的原理。按照我的個(gè)人理解 ,是因?yàn)閮?nèi)存溢出造成的。內(nèi)存泄露指的是 原本應(yīng)該回收的對(duì)象,現(xiàn)在由于種種原因,無(wú)法被回收。

為什么上面會(huì)強(qiáng)調(diào) 配合線(xiàn)程池的時(shí)候,因?yàn)閱为?dú)線(xiàn)程的時(shí)候,當(dāng)線(xiàn)程任務(wù)運(yùn)行完以后,線(xiàn)程資源會(huì)被回收,自然 本地副本也被回收了。而線(xiàn)程池里面的線(xiàn)程不全被回收(有的不會(huì)被回收,也有的會(huì)被回收)。

現(xiàn)在來(lái)看一下上面的Entry這個(gè)最終存儲(chǔ)本地副本的靜態(tài)內(nèi)部類(lèi),

   static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

下面內(nèi)容需要你對(duì) java 內(nèi)存管理關(guān)系了解,否則 你肯定會(huì)一臉蒙蔽。

如果 你不會(huì) 可以看我這篇文章java內(nèi)存管理關(guān)系及內(nèi)存泄露的原理

由于它是WeakReference的子類(lèi),所以 作為引用對(duì)象的 ThreadLocal,就有可能會(huì)被Entry清除引用。如果這時(shí)候 ThreadLocal沒(méi)有其他的引用,那么它肯定就會(huì)被GC回收了。

但是value 是強(qiáng)引用,而Entry 又被Entry[]持有,Entry[]又被ThreadLocalMap持有,ThreadLocalMap又被線(xiàn)程持有。只要線(xiàn)程不死或者 你不調(diào)用set,remove這兩個(gè)方法之中任何一個(gè),那么value指向的這個(gè)對(duì)象就始終 不會(huì)被回收。因?yàn)?不符合GC回收的兩個(gè)條件的任何一個(gè)。

試想一下如果線(xiàn)程池里面的線(xiàn)程足夠的多,并且 你傳給線(xiàn)程的本地副本內(nèi)存占用又很大。毫無(wú)疑問(wèn) 會(huì)內(nèi)存溢出。

解決方法

只要調(diào)用remove 這個(gè)方法會(huì)擦出 上一個(gè)value的引用,這樣線(xiàn)程就不會(huì)持有上一個(gè)value指向?qū)ο蟮囊?。就不?huì)有內(nèi)存露出了。

有讀者會(huì)有疑問(wèn)了,上面不是說(shuō)兩個(gè)放過(guò)會(huì)使value對(duì)象可以回收么,怎么上面沒(méi)有set方法呢?

這個(gè)是因?yàn)?,set方法確實(shí)可以是value指向的對(duì)象 這個(gè)引用斷開(kāi),但同時(shí)它又強(qiáng)引用了一個(gè)內(nèi)存空間給value。即使上一個(gè)對(duì)象被回收了,但是新對(duì)象也產(chǎn)生了。

至于 get方法,只有在ThreadLocalMap 被GC后,調(diào)用get方法 才會(huì)將value對(duì)應(yīng)的引用切斷。

首先,我們看get源碼

  public T get() {
        Thread t = Thread.currentThread();//當(dāng)前線(xiàn)程的引用
        //得到當(dāng)前線(xiàn)程的ThreadLocalMap,如果沒(méi)有返回null
        ThreadLocalMap map = getMap(t);
        //存在時(shí)候走這個(gè)
        if (map != null) {
             //與鍵關(guān)聯(lián)的項(xiàng),如果沒(méi)有鍵則為null  
             //如果ThreadLocalMap的entry 清除了ThreadLocal 對(duì)象的引用,那么這個(gè)會(huì)清除對(duì)應(yīng)的value 引用
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //當(dāng)前線(xiàn)程 沒(méi)有設(shè)置ThreadLocalMap,那么返回initialValue()的值
        return setInitialValue();
    }

上面這段代碼,調(diào)用了getEntry,這個(gè)方法內(nèi)部調(diào)用了 另一個(gè)方法,實(shí)現(xiàn)了當(dāng)ThreadLocal被清除引用后,也清除對(duì)應(yīng)的value引用,

    private Entry getEntry(ThreadLocal<?> key) {
            //得到位置  table數(shù)組 的容量是16
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
             //key沒(méi)有被回收后
            if (e != null && e.get() == key)
                return e;
            else
                 //這個(gè)key被回收 調(diào)用,將對(duì)應(yīng)的value 釋放引用
                return getEntryAfterMiss(key, i, e);
        }

我們看見(jiàn)最后調(diào)用 getEntryAfterMiss(key, i, e),這個(gè)方法 也不是最終的擦除value引用的方法,我們接著往下看

 private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;
            while (e != null) {
                 //得到弱引用對(duì)象 持有的引用對(duì)象的引用
                ThreadLocal<?> k = e.get();
                //ThreadLocal沒(méi)有被回收
                if (k == key)
                    return e;
                
                if (k == null)
                    //entry 清除ThreadLocal的引用 
                   //通過(guò)entry[]數(shù)組的元素entry 清除entry的value引用
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

這上面呢,我們要關(guān)注expungeStaleEntry(i),這個(gè)才是最終的擦除entry的value對(duì)象的引用。 看一下 expungeStaleEntry(i)的源碼

 private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;//得到table引用
            int len = tab.length;//得到table的長(zhǎng)度,不出意外 應(yīng)該是16
            // expunge entry at staleSlot
           //下面兩句代碼 是關(guān)鍵。
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;
            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;
                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

上面這段代碼很長(zhǎng),我們不必細(xì)看個(gè),關(guān)注下面這兩行代碼就行

            tab[staleSlot].value = null;//清除引用  這樣 GC就可以回收了
            tab[staleSlot] = null;//清除自身的引用

通過(guò)entry[staleSlot]得到存儲(chǔ)的entry ,通過(guò)entry清除entry的value引用。

這樣大家明白了吧,get也是可以起到和remove一樣的效果的。

我們?cè)倏匆幌聄emove的源碼

 public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

上面這段代碼沒(méi)什么說(shuō)的,直接看ThreadLocalMap的remove方法

    private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            //得到位置,因?yàn)榇娴臅r(shí)候 也是按照這個(gè)規(guī)則來(lái)的,
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                 //這里有可能會(huì)發(fā)生 ThreadLocal 被entry清除引用,那么value就被線(xiàn)程引用了,如果不調(diào)用set,get方法的話(huà),只能等待線(xiàn)程銷(xiāo)毀。
                if (e.get() == key) {
                    //調(diào)用弱引用的方法 , 將引用對(duì)象的引用清除
                    e.clear();
                    //擦出ThreadLocal 對(duì)應(yīng)的value
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

上面調(diào)用了 expungeStaleEntry 擦除。

set

我們關(guān)注這個(gè)方法

  private void expungeStaleEntries() {
            Entry[] tab = table;
            int len = tab.length;
            for (int j = 0; j < len; j++) {
                Entry e = tab[j];
                if (e != null && e.get() == null)
                    //擦除
                    expungeStaleEntry(j);
            }
        }

這個(gè)呢 循環(huán)調(diào)用了expungeStaleEntry(j)方法 ,也是擦除了value的對(duì)象引用。

為什么要將ThreadLocal 定義成 static 變量

延長(zhǎng)生命周期,之所以是static 是因?yàn)椋琓hreadLocal 我們更應(yīng)該將他看成是 工具。

對(duì)ThreadLocal內(nèi)存泄漏引起的思考

概述

最近在對(duì)一個(gè)項(xiàng)目進(jìn)行重構(gòu),用到了ThreadLocal。

場(chǎng)景如下:

外圍系統(tǒng)會(huì)調(diào)用接口上傳數(shù)據(jù),在接口中要記錄數(shù)據(jù)的變化Id,在上傳數(shù)據(jù)完后需要集中在一個(gè)地方把這些Id以消息形式發(fā)送出去。

使用場(chǎng)景樣例代碼

    public Result<Void> uploadOrder(TotalPayInfoVo totalPayInfoVo) {
        try {
            saveTotalPayInfoVo(totalPayInfoVo);
            //發(fā)送消息
            UnitWork.getCurrent().pushMessage();
        } catch (Exception e) {
            cashLogger.error("uploadOrder error,data: {}, error: {}", JSON.toJSONString(totalPayInfoVo), e);
            throw new RuntimeException("保存失敗", e);
        } finally {
            UnitWork.clean();//
        }
        return ResultUtil.successResult();避免內(nèi)存泄漏
    }

ThreadLocal使用源碼

/**
 * 工作單元,在同一個(gè)線(xiàn)程中負(fù)責(zé)記錄一個(gè)事件或者一個(gè)方法或者一個(gè)事務(wù)過(guò)程中產(chǎn)生的變化,等操作結(jié)束后再處理這種變化。
 */
public class UnitWork {
    private UnitWork() {
    }
    private static ThreadLocal<UnitWork> current = new ThreadLocal<UnitWork>() {
        protected UnitWork initialValue() {
            return new UnitWork();
        }
    };
    /**
     * 狀態(tài)變化的instance
     */
    private Set<String> statusChangedInstances = new HashSet<>();
    public void addStatusChangedInstance(String instance) {
        statusChangedInstances.add(instance);
    }
    /**
     * 推送消息
     */
    public void pushMessage() {
       for(String id : statusChangedInstances){
            //異步發(fā)消息
       }
    }
    public static UnitWork getCurrent() {
        return current.get();
    }
    /**
     * 刪除當(dāng)前線(xiàn)程的工作單元,建議放在finally中調(diào)用,避免內(nèi)存泄漏
     */
    public static void clean() {
        current.remove();
    }
}

思考問(wèn)題

為了避免內(nèi)存泄漏,每次用完做一下clean清理操作。發(fā)送消息的過(guò)程是異步的,意味著clean的時(shí)候可能和發(fā)送消息同時(shí)進(jìn)行。那么會(huì)不會(huì)把這些Id清理掉?那么可能造成消息發(fā)送少了。要回答這個(gè)問(wèn)題,首先要搞懂ThreadLocal的引用關(guān)系,remove操作做了什么?

ThreadLocal解讀

ThreadLocal可以分別在各個(gè)線(xiàn)程保存變量獨(dú)立副本。每個(gè)線(xiàn)程都有ThreadLocalMap,顧名思義,類(lèi)似Map容器,不過(guò)是用數(shù)組Entry[]來(lái)模擬的。那么既然類(lèi)似Map,肯定會(huì)存在Key。其實(shí)Key是ThreadLocal類(lèi)型,Key的值是ThreadLocal的HashCode,即通過(guò)threadLocalHashCode計(jì)算出來(lái)的值。

這個(gè)Map的Entry并不是ThreadLocal,而是一個(gè)帶有弱引用的Entry。既然是弱引用,每次GC的時(shí)候都會(huì)回收。

        static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

而Key對(duì)應(yīng)的value就是要保存在線(xiàn)程副本Object,這里指的就是UnitWork的實(shí)例。調(diào)用ThreadLocal的get方法時(shí),首先找到當(dāng)前線(xiàn)程的ThreadLocalMap,然后根據(jù)這個(gè)ThreadLocal算出來(lái)的hashCode找到保存線(xiàn)程副本Object。

他們的關(guān)系對(duì)應(yīng)如下:

這里寫(xiě)圖片描述

ThreadLocal在remove的時(shí)候,會(huì)調(diào)用Entry的clear,即弱引用的clear方法。把Key->ThreadLocal的引用去掉。接下來(lái)的expungeStaleEntry會(huì)把entry中value引用設(shè)置為null。

        /**
         * Remove the entry for key.
         */
        private void remove(ThreadLocal key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

現(xiàn)在可以回答之前提前的問(wèn)題。雖然ThreadLocal和當(dāng)前線(xiàn)程都會(huì)與Object脫離了引用的關(guān)系,但是最重要一點(diǎn)就是異步的線(xiàn)程仍然存在一條強(qiáng)引用路徑到Object,即到UnitWork實(shí)例的強(qiáng)引用。因此GC然后不會(huì)回收UnitWork的實(shí)例,發(fā)消息還是不會(huì)少發(fā)或者出現(xiàn)空指針情況。

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 在Spring Boot中處理文件上傳功能實(shí)現(xiàn)

    在Spring Boot中處理文件上傳功能實(shí)現(xiàn)

    這篇文章主要介紹了如何在Spring Boot中處理文件上傳,通過(guò)配置文件上傳屬性、創(chuàng)建控制器來(lái)處理上傳的文件,并通過(guò)異常處理器來(lái)管理錯(cuò)誤情況,可以快速實(shí)現(xiàn)文件上傳功能,需要的朋友可以參考下
    2024-06-06
  • Jtable和JTree的寫(xiě)法示例代碼

    Jtable和JTree的寫(xiě)法示例代碼

    這篇文章主要介紹了Jtable和JTree的基本概念,常用構(gòu)造方法,以及二者的通用寫(xiě)法,需要的朋友可以了解下。
    2017-09-09
  • Java實(shí)現(xiàn)ATM取款機(jī)程序

    Java實(shí)現(xiàn)ATM取款機(jī)程序

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)ATM取款機(jī)程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-01-01
  • Java基礎(chǔ)篇_有關(guān)接口和抽象類(lèi)的幾道練習(xí)題(分享)

    Java基礎(chǔ)篇_有關(guān)接口和抽象類(lèi)的幾道練習(xí)題(分享)

    下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)篇_有關(guān)接口和抽象類(lèi)的幾道練習(xí)題(分享)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-06-06
  • Eclipse安裝配置方法圖文教程

    Eclipse安裝配置方法圖文教程

    這篇文章主要為大家詳細(xì)介紹了Eclipse安裝配置方法圖文教程,感興趣的小伙伴們可以參考一下
    2016-06-06
  • 深入了解Java File分隔符和Path分隔符的使用

    深入了解Java File分隔符和Path分隔符的使用

    不同的操作系統(tǒng)使用不同的字符作為文件和路徑分隔符。當(dāng)我們的應(yīng)用程序需要在多個(gè)平臺(tái)上運(yùn)行時(shí),我們需要正確處理這些問(wèn)題。Java幫助我們選擇一個(gè)合適的分隔符,本文就來(lái)聊聊Java中File分隔符和 Path分隔符的使用
    2022-07-07
  • Mybatis-Plus如何實(shí)現(xiàn)時(shí)間日期的比較

    Mybatis-Plus如何實(shí)現(xiàn)時(shí)間日期的比較

    文章主要討論了在使用Mybatis-Plus進(jìn)行數(shù)據(jù)庫(kù)查詢(xún)時(shí),如何正確處理日期和時(shí)間的比較,作者強(qiáng)調(diào)了使用數(shù)據(jù)庫(kù)函數(shù)進(jìn)行比較的重要性,避免了直接將時(shí)間轉(zhuǎn)換為字符串進(jìn)行比較的錯(cuò)誤,同時(shí),作者建議在需要比較天數(shù)時(shí)使用LocalDate
    2024-11-11
  • Netty中序列化的作用及自定義協(xié)議詳解

    Netty中序列化的作用及自定義協(xié)議詳解

    這篇文章主要介紹了Netty中序列化的作用及自定義協(xié)議詳解,Netty自身就支持很多種協(xié)議比如Http、Websocket等等,但如果用來(lái)作為自己的RPC框架通常會(huì)自定義協(xié)議,所以這也是本文的重點(diǎn),需要的朋友可以參考下
    2023-12-12
  • SpringBoot實(shí)現(xiàn)本地存儲(chǔ)文件上傳及提供HTTP訪(fǎng)問(wèn)服務(wù)的方法

    SpringBoot實(shí)現(xiàn)本地存儲(chǔ)文件上傳及提供HTTP訪(fǎng)問(wèn)服務(wù)的方法

    這篇文章主要介紹了SpringBoot實(shí)現(xiàn)本地存儲(chǔ)文件上傳及提供HTTP訪(fǎng)問(wèn)服務(wù),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-08-08
  • Java實(shí)現(xiàn)動(dòng)態(tài)規(guī)劃背包問(wèn)題

    Java實(shí)現(xiàn)動(dòng)態(tài)規(guī)劃背包問(wèn)題

    本文主要介紹使用java實(shí)現(xiàn)動(dòng)態(tài)規(guī)劃的背包問(wèn)題,詳細(xì)使用圖文和多種案例進(jìn)行解析,幫助理解該算法
    2021-06-06

最新評(píng)論

潍坊市| 麟游县| 宝清县| 金秀| 黄陵县| 隆德县| 永胜县| 金沙县| 嘉定区| 志丹县| 昌黎县| 沅江市| 海南省| 阿拉尔市| 新源县| 新密市| 同心县| 拉萨市| 萨迦县| 兴安县| 洛隆县| 泌阳县| 遂昌县| 紫阳县| 昌图县| 延安市| 进贤县| 龙川县| 资兴市| 崇礼县| 灵武市| 陇川县| 东乌珠穆沁旗| 商水县| 兴国县| 富宁县| 香格里拉县| 刚察县| 梧州市| 宜良县| 西丰县|