欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入淺出解析Java ThreadLocal原理

 更新時(shí)間:2021年11月11日 10:58:17   作者:ATFWUS  
ThreadLocal是JDK包提供的,它提供線程本地變量,如果創(chuàng)建一樂ThreadLocal變量,那么訪問這個(gè)變量的每個(gè)線程都會(huì)有這個(gè)變量的一個(gè)副本,在實(shí)際多線程操作的時(shí)候,操作的是自己本地內(nèi)存中的變量,從而規(guī)避了線程安全問題,感興趣的朋友快來看看吧

分享一下最近看的ThreadLocal的源碼的一些體會(huì)。

1.了解ThreadLocal

簡介

  • ThreadLocal是JDK中java.lang包下提供的類。
  • ThreadLocal是線程安全的,并且沒有使用到鎖。
  • 常用來存放線程獨(dú)有變量,解決參數(shù)傳遞問題。
  • 當(dāng)我們創(chuàng)建一個(gè)ThreadLocal包裝的變量后,每個(gè)訪問這個(gè)變量的線程會(huì)在自己的線程空間創(chuàng)建這個(gè)變量的一個(gè)副本,在每次操作這個(gè)變量的時(shí)候,都是在自己的線程空間內(nèi)操作,解決了線程安全問題。

在這里插入圖片描述

使用

  • (是線程安全的) 在這個(gè)demo中,localStr是共享的,隨后在每個(gè)線程中給localStr設(shè)置值為自己線程的名字,然后再將當(dāng)前線程的日志輸出。
  • sleep5毫秒是為了體現(xiàn)出是否存在線程安全問題。
  • 從運(yùn)行結(jié)果可以看到,是不存在線程安全問題的:
/**
 * @author ATFWUS
 * @version 1.0
 * @date 2021/11/8 21:23
 * @description
 */
@Slf4j
public class ThreadLocalTest {

    static ThreadLocal<String> localStr = new ThreadLocal<>();

    public static void main(String[] args) {
        List<Thread> list = new LinkedList<>();
        for(int i = 0; i < 1000; i++){
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    localStr.set(Thread.currentThread().getName() + " localStr");
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    log.debug(localStr.get());
                }
            }, "t" + String.valueOf(i));
            list.add(t);
        }
        for (Thread t : list) {
            t.start();
        }

    }
}

在這里插入圖片描述

而對(duì)于普通變量來說,很明顯是存在線程安全問題的:

/**
 * @author ATFWUS
 * @version 1.0
 * @date 2021/11/8 21:23
 * @description
 */
@Slf4j
public class ThreadLocalTest {

    static ThreadLocal<String> localStr = new ThreadLocal<>();
    static String shareStr;

    public static void main(String[] args) {
        List<Thread> list = new LinkedList<>();
        for(int i = 0; i < 1000; i++){
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    shareStr = Thread.currentThread().getName() + "  shareStr";
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    log.debug(shareStr);
                }
            }, "t" + String.valueOf(i));
            list.add(t);
        }
        for (Thread t : list) {
            t.start();
        }

    }
}

在這里插入圖片描述

2.源碼解析 – 探究實(shí)現(xiàn)思路

threadLocals變量與ThreadLocalMap

  • 每個(gè)線程的本地變量并不存放于ThreadLocal對(duì)象中,而是存在調(diào)用線程的threadLocals變量中。因?yàn)槭蔷€程對(duì)象的成員變量,所以生命周期等同于線程的生命周期。

在這里插入圖片描述

  • 而threadLocals是ThreadLocalMap類的實(shí)例。
  • ThreadLocalMap實(shí)際上是一個(gè)類似HashMap的實(shí)現(xiàn),是ThreadLocal的靜態(tài)內(nèi)部類。
  • 看下Doug Lea寫的注釋: ThreadLocalMap是一個(gè)定制的hash map,僅適用于維護(hù)線程本地值。在ThreadLocal類之外沒有暴露任何的操作。這個(gè)類是私有的,允許在類線程中聲明字段。為了處理非常大并長期存在(對(duì)象)的用法,哈希表的entries使用weakReference作為鍵。但是,由于沒有使用引用隊(duì)列,因此只有當(dāng)表開始耗盡空間時(shí),才能保證刪除過時(shí)的entries。

在這里插入圖片描述

  • 暫不探究ThreadLocalMap的內(nèi)部實(shí)現(xiàn)細(xì)節(jié),暫時(shí)只需要知道實(shí)現(xiàn)了一個(gè)hash map,并且Entry的key是弱引用即可,具體的set() get() remove() 方法在下文中會(huì)有。

set(T value) 方法

  • 進(jìn)入set(T value) 方法后,先嘗試獲取map,如果獲取到了map,直接設(shè)置值,否則新建一個(gè)map。
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

get() 方法

  1. 進(jìn)入get()方法后,首先獲取當(dāng)前線程,然后進(jìn)入getMap(Thread t)中獲取ThreadLocalMap對(duì)象,直接返回t.threadLocals。
  2. 如果map不為空,直接返回map中當(dāng)前ThreadLocal作為鍵對(duì)應(yīng)的值。
  3. 如果map為空,需要先進(jìn)行初始化。調(diào)用setInitialValue()方法進(jìn)行初始化。
  • setInitialValue()中先獲取一個(gè)初始值,默認(rèn)為null。
  • 如果map存在當(dāng)前線程中,直接設(shè)置初始值。
  • 如果map不存在當(dāng)前線程中,需要先創(chuàng)建一個(gè)map。
  • createMap(Thread t, T firstValue)中就是new了一個(gè)ThreadLocalMap對(duì)象,并且初始化了一個(gè)entry對(duì)。
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
    protected T initialValue() {
        return null;
    }
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

remove() 方法

  • remove() 方法中,先判斷map是否存在,不存在直接將map中this作為鍵的entry刪掉。
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

實(shí)現(xiàn)思路總結(jié)

  • ThreadLocal搭配線程的threadLocals變量實(shí)現(xiàn),當(dāng)調(diào)用set(T value) 和 get() 方法時(shí),如果線程中的threadLocals仍然為null,會(huì)為其初始化。
  • ThreadLocal對(duì)象往threadLocals存儲(chǔ)具體變量時(shí),key是ThreadLocal對(duì)象的自身引用,value是真正的變量,且key是弱引用。

在這里插入圖片描述

3.InheritableThreadLocal與繼承性

InheritableThreadLocal英語翻譯一下就是可繼承的ThreadLocal,讓我們看下它和ThreadLocal的繼承性體現(xiàn)在哪。

這里的繼承性指的是:子線程是否能訪問父線程的變量。

ThreadLocal的不可繼承性

threadLocals是當(dāng)前線程的成員變量,在子線程中不可見

/**
 * @author ATFWUS
 * @version 1.0
 * @date 2021/11/9 14:29
 * @description
 */
@Slf4j
public class InheritableThreadLocalTest {

    static ThreadLocal<String> localStr = new ThreadLocal<>();

    public static void main(String[] args) {
        localStr.set("main線程為其設(shè)置的值");
        new Thread(new Runnable() {
            @Override
            public void run() {
                log.debug("訪問localStr : " + localStr.get());
            }
        }).start();
        System.out.println(localStr.get());
    }
}

在這里插入圖片描述

InheritableThreadLocal實(shí)現(xiàn)繼承性的源碼剖析

看一下InheritableThreadLocal的源碼:

在這里插入圖片描述

源碼非常簡短,下面簡單分析一下:

  • InheritableThreadLocal類繼承自ThreadLocal類,重寫了childValue(T parentValue)、getMap()、createMap(Thread t, T firstValue) 三個(gè)方法。
  • createMap(Thread t, T firstValue)會(huì)在初始化的時(shí)候調(diào)用,重寫createMap(Thread t, T firstValue) 意味著,InheritableThreadLocal的實(shí)例使用的是線程對(duì)象中的inheritableThreadLocals,而不再是原來的threadLocals。
  • getMap() 方法也是確保使用的是inheritableThreadLocals。
  • childValue(T parentValue) 方法中,直接返回了parentValue,這個(gè)方法會(huì)在ThreadLocal的構(gòu)造方法中被調(diào)用,為了弄清這個(gè)意圖,我們有必要看看Thread類初始化方法的源碼。

從Thread的構(gòu)造方法看,發(fā)現(xiàn)所有的構(gòu)造方法都會(huì)調(diào)用init()方法進(jìn)行初始化,init()方法有兩個(gè)重載形式。

在這里插入圖片描述

我們進(jìn)入?yún)?shù)較多的init方法查看一下:

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;
				// 新線程還未創(chuàng)建出來,當(dāng)前線程就是即將要?jiǎng)?chuàng)建線程的父線程
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        // 如果父線程的inheritThreadLocals 不為空
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        		 // 設(shè)置子線程中的inheritableThreadLocals設(shè)置為父線程的inheritableThreadLocals
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

我們重點(diǎn)看一下和inheritThreadLocals相關(guān)的地方(含注釋的地方)

  • 在進(jìn)入init方法后,先獲取了父線程,然后再下面判斷了父線程的inheritThreadLocals 是否為空,不為空就調(diào)用ThreadLocal.createInheritedMap方法,參數(shù)就是父線程的inheritThreadLocals 。

再看下ThreadLocal.createInheritedMap方法:

  • 調(diào)用了自身的構(gòu)造方法,將parentMap傳入。
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

看下這個(gè)構(gòu)造方法:

  • 發(fā)現(xiàn)主要是用parentMap的所有entry初始化當(dāng)前的map。
  • 在注釋處,調(diào)用了inheritThreadLocals重寫的childValue方法,而重寫后,直接返回的是parentValue,也就是將父線程的inheritThreadLocal里面的entry完整的復(fù)制到了子線程中。
        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        // 調(diào)用inheritThreadLocals重寫的childValue方法
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

如何理解這個(gè)繼承性

通過上面的源碼分析,可以發(fā)現(xiàn),InheritableThreadLocal的繼承性主要體現(xiàn)在:創(chuàng)建子線程時(shí),會(huì)將父線程的inheritThreadLocals里面所有entry拷貝一份給子進(jìn)程。

那么當(dāng)子進(jìn)程被創(chuàng)建出來之后,父進(jìn)程又修改了inheritThreadLocals里面的值,這個(gè)操作是否對(duì)子線程可見,通過上面的源碼可知,這個(gè)操作明顯是不可見的,下面有個(gè)demo可以證實(shí)。

  • sleep操作是為了控制兩個(gè)線程的執(zhí)行流程。
/**
 * @author ATFWUS
 * @version 1.0
 * @date 2021/11/9 14:29
 * @description
 */
@Slf4j
public class InheritableThreadLocalTest {

    static ThreadLocal<String> localStr = new ThreadLocal<>();
    static InheritableThreadLocal<String> inheritableLocalStr = new InheritableThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        inheritableLocalStr.set("main線程第一次為inheritableLocalStr設(shè)置的值");
        new Thread(new Runnable() {
            @Override
            public void run() {
                log.debug("子線程第一次訪問inheritableLocalStr : " + inheritableLocalStr.get());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("子線程第二次訪問inheritableLocalStr : " + inheritableLocalStr.get());
            }
        }).start();
        Thread.sleep(500);
        inheritableLocalStr.set("main線程第二次為inheritableLocalStr設(shè)置的值");
        log.debug("main線程第二次為inheritableLocalStr賦值");
        Thread.sleep(1000);
    }
}

看下輸出:

在這里插入圖片描述

可以發(fā)現(xiàn),子線程創(chuàng)建出來后,對(duì)父線程中inheritThreadLocals的修改操作,對(duì)子線程不可見。

總結(jié)

  • ThreadLocal不可繼承,threadLocals是當(dāng)前線程的成員變量,在子線程中不可見。
  • InheritableThreadLocal可繼承,原理是:在新建子線程的時(shí)候,將父線程中inheritThreadLocals所有的entry拷貝給了子線程。
  • 子線程創(chuàng)建出來后,對(duì)父線程中inheritThreadLocals的修改操作,對(duì)子線程不可見。

4.存在的內(nèi)存泄露問題

要充分理解ThreadLocal中存在的內(nèi)存泄露問題,需要有以下JVM對(duì)內(nèi)存管理的前置知識(shí)(這里篇幅問題就不補(bǔ)充了):

  • 什么是內(nèi)存泄露?
  • 什么是強(qiáng)引用?
  • 什么是弱引用?
  • 何時(shí)GC?
  • 強(qiáng)引用和弱引用GC時(shí)的區(qū)別?

在分析上述ThreadLocalMap源碼的時(shí)候,注意到有一個(gè)小細(xì)節(jié),ThreadLocalMap的Entry繼承了WeakReference<ThreadLocal<?>>,也就是說Entry的key是一個(gè)對(duì)ThreadLocal<?>的弱引用。問題來了,為什么這里要使用弱引用呢?

在這里插入圖片描述

使用強(qiáng)引用會(huì)如何?

現(xiàn)在假設(shè)Entry的key是一個(gè)對(duì)ThreadLocal的強(qiáng)引用,當(dāng)ThreadLocal對(duì)象使用完后,外部的強(qiáng)引用不存在,但是因?yàn)楫?dāng)前線程對(duì)象中的threadLocals還持有ThreadLocal的強(qiáng)引用,而threadLocals的生命周期是和線程一致的,這個(gè)時(shí)候,如果沒有手動(dòng)刪除,整個(gè)Entry就發(fā)生了內(nèi)存泄露。

使用弱引用會(huì)如何?

現(xiàn)在假設(shè)Entry的key是一個(gè)對(duì)ThreadLocal的弱引用,當(dāng)ThreadLocal對(duì)象使用完后,外部的強(qiáng)引用不存在,此時(shí)ThreadLocal對(duì)象只存在Entry中key對(duì)它的弱引用,在下次GC的時(shí)候,這個(gè)ThreadLocal對(duì)象就會(huì)被回收,導(dǎo)致key為null,此時(shí)value的強(qiáng)引用還存在,但是value已經(jīng)不會(huì)被使用了,如果沒有手動(dòng)刪除,那么這個(gè)Entry中的key就會(huì)發(fā)生內(nèi)存泄露。

使用弱引用還有一些好處,那就是,當(dāng)key為null時(shí), ThreadLocalMap中最多存在一個(gè)key為null,并且當(dāng)調(diào)用set(),get(),remove()這些方法的時(shí)候,是會(huì)清除掉key為null的entry的。

set()、get()、remove() 方法中相關(guān)實(shí)現(xiàn)

  • 從下可以發(fā)現(xiàn),set方法首先會(huì)進(jìn)入一個(gè)循環(huán)。
  • 在這個(gè)循環(huán)中,會(huì)遍歷整個(gè)Entry數(shù)組。直到遇到一個(gè)空的entry,退出循環(huán)。
  • 當(dāng)遇到已存在的key'時(shí),會(huì)直接替換value,然后返回。
  • 當(dāng)遇到key為空的entry的時(shí)候,會(huì)直接將當(dāng)前的entry存在這個(gè)過時(shí)的entry中,然后返回。

通過這個(gè)方法的源碼可以看出,key為null的那個(gè)entry實(shí)際上遲早會(huì)被替換成新的entry。

        private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            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)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }
							 // 發(fā)現(xiàn)key為空
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
        
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

同理,可以看到在get方法中也存在:

        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }
         private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                // 替換過時(shí)的entry
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

remove() 方法中也是一樣:

        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)]) {
                // 清除過時(shí)的key
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

總結(jié)

  • ThreadLocal如果對(duì)ThreadLocalMap的key使用強(qiáng)引用,那么會(huì)存在整個(gè)entry發(fā)生內(nèi)存泄露的問題,如果不手動(dòng)清除,那么這個(gè)不被使用的entry會(huì)一直存在。
  • ThreadLocal如果對(duì)ThreadLocalMap的key使用弱引用,那么可能會(huì)存在一個(gè)entry的value發(fā)生內(nèi)存泄露,但是在調(diào)用set(),get(),remove() 方法時(shí),key為null的entry會(huì)被清除掉。
  • 發(fā)生內(nèi)存泄露最根本的原因是:threadLocals的生命周期是和線程一致的。
  • 每次使用完ThreadLocal對(duì)象后,必須調(diào)用它的remove()方法清除數(shù)據(jù)。

5.ThreadLocal應(yīng)用

ThreadLocal把數(shù)據(jù)存放到線程本地,解決了線程安全問題,沒有使用鎖,直接訪問線程本地變量,效率較高(空間換時(shí)間。)
同時(shí)threadLocals的生命周期是和線程一致的,可以解決很多參數(shù)傳遞問題。

  • Session管理(Mabaties使用ThreadLocal存儲(chǔ)session),數(shù)據(jù)庫連接。
  • 如果需要跟蹤請(qǐng)求的整個(gè)流程,可以使用ThreadLocal來傳遞參數(shù)。

ATFWUS 2021-11-11

以上就是深入淺出解析Java ThreadLocal原理的詳細(xì)內(nèi)容,更多關(guān)于Java ThreadLocal原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java的反射機(jī)制之類加載詳解

    Java的反射機(jī)制之類加載詳解

    這篇文章主要介紹了Java的反射機(jī)制之類加載詳解,反射機(jī)制是java實(shí)現(xiàn)動(dòng)態(tài)語言的關(guān)鍵,也就是通過反射實(shí)現(xiàn)類動(dòng)態(tài)加載,靜態(tài)加載是指在編譯時(shí)期確定要加載的類的類型,即通過class關(guān)鍵字和類名來獲取對(duì)應(yīng)類的類型,需要的朋友可以參考下
    2023-09-09
  • 23種設(shè)計(jì)模式(11)java策略模式

    23種設(shè)計(jì)模式(11)java策略模式

    這篇文章主要為大家詳細(xì)介紹了23種設(shè)計(jì)模式之java策略模式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • SpringBoot整合MongoDB實(shí)現(xiàn)文件上傳下載刪除

    SpringBoot整合MongoDB實(shí)現(xiàn)文件上傳下載刪除

    這篇文章主要介紹了SpringBoot整合MongoDB實(shí)現(xiàn)文件上傳下載刪除的方法,幫助大家更好的理解和學(xué)習(xí)使用SpringBoot框架,感興趣的朋友可以了解下
    2021-05-05
  • SpringBoot中如何啟動(dòng)Tomcat流程

    SpringBoot中如何啟動(dòng)Tomcat流程

    這篇文章主要介紹了SpringBoot中如何啟動(dòng)Tomcat流程,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2019-05-05
  • Java中自動(dòng)生成構(gòu)造方法詳解

    Java中自動(dòng)生成構(gòu)造方法詳解

    這篇文章主要介紹了Java中自動(dòng)生成構(gòu)造方法詳解的相關(guān)資料,需要的朋友可以參考下
    2017-04-04
  • Java 十大排序算法之冒泡排序刨析

    Java 十大排序算法之冒泡排序刨析

    冒泡排序是一種簡單的排序算法,它也是一種穩(wěn)定排序算法。其實(shí)現(xiàn)原理是重復(fù)掃描待排序序列,并比較每一對(duì)相鄰的元素,當(dāng)該對(duì)元素順序不正確時(shí)進(jìn)行交換。一直重復(fù)這個(gè)過程,直到?jīng)]有任何兩個(gè)相鄰元素可以交換,就表明完成了排序
    2021-11-11
  • SpringBoot如何手寫一個(gè)starter并使用這個(gè)starter詳解

    SpringBoot如何手寫一個(gè)starter并使用這個(gè)starter詳解

    starter是SpringBoot中的一個(gè)新發(fā)明,它有效的降低了項(xiàng)目開發(fā)過程的復(fù)雜程度,對(duì)于簡化開發(fā)操作有著非常好的效果,下面這篇文章主要給大家介紹了關(guān)于SpringBoot如何手寫一個(gè)starter并使用這個(gè)starter的相關(guān)資料,需要的朋友可以參考下
    2022-12-12
  • Java 中實(shí)現(xiàn)隨機(jī)無重復(fù)數(shù)字的方法

    Java 中實(shí)現(xiàn)隨機(jī)無重復(fù)數(shù)字的方法

    為了更好地理解這個(gè)題意,我們先來看下具體內(nèi)容:生成一個(gè)1-100 的隨機(jī)數(shù)組,但數(shù)組中的數(shù)字不能重復(fù),即位置是隨機(jī)的,但數(shù)組元素不能重復(fù)
    2013-03-03
  • Java反射機(jī)制的實(shí)現(xiàn)詳解

    Java反射機(jī)制的實(shí)現(xiàn)詳解

    反射主要解決動(dòng)態(tài)編程,即使用反射時(shí),所有的對(duì)象生成是動(dòng)態(tài)的,因此調(diào)用的方法也是動(dòng)態(tài)的.反射可以簡化開發(fā),但是代碼的可讀性很低
    2013-05-05
  • 手把手搭建Java共享網(wǎng)盤的方法步驟

    手把手搭建Java共享網(wǎng)盤的方法步驟

    這篇文章主要介紹了手把手搭建Java共享網(wǎng)盤,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12

最新評(píng)論