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

Java中的ThreadLocal線程池原理

 更新時間:2023年11月20日 10:58:28   作者:?薄情痞子?  
這篇文章主要介紹了Java中的ThreadLocal線程池原理,ThreadLocal提供了線程的局部變量(或本地變量),它可以保證訪問到的變量屬于當(dāng)前線程,每個訪問這種變量的線程(通過它的get或set方法)都有自己的、獨立初始化的變量副本,需要的朋友可以參考下

ThreadLocal

ThreadLocal提供了線程的局部變量(或本地變量)。

它可以保證訪問到的變量屬于當(dāng)前線程,每個訪問這種變量的線程(通過它的get或set方法)都有自己的、獨立初始化的變量副本,每個線程的變量都不同。

ThreadLocal相當(dāng)于提供了一種線程隔離,將變量與線程相綁定。

ThreadLocal類定義如下:可以簡單瞄一眼吆,畢竟沒有瀏覽的欲望....

public class ThreadLocal<T> {
   
    private final int threadLocalHashCode = nextHashCode();
    private static AtomicInteger nextHashCode = new AtomicInteger();
    private static final int HASH_INCREMENT = 0x61c88647;
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
 
    protected T initialValue() {
        return null;
    }
 
    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }
 
    public ThreadLocal() {
    }
 
    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();
    }
 
    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;
    }
 
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
 
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
 
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
 
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
 
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }
 
    T childValue(T parentValue) {
        throw new UnsupportedOperationException();
    }
 
    static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
 
        private final Supplier<? extends T> supplier;
 
        SuppliedThreadLocal(Supplier<? extends T> supplier) {
            this.supplier = Objects.requireNonNull(supplier);
        }
 
        @Override
        protected T initialValue() {
            return supplier.get();
        }
    }
 
    static class ThreadLocalMap {
 
        static class Entry extends WeakReference<ThreadLocal<?>> {
         
            Object value;
 
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
 
       
        private static final int INITIAL_CAPACITY = 16;
 
        
        private Entry[] table;
 
       
        private int size = 0;
 
       
        private int threshold; // Default to 0
 
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }
 
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }
 
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }
 
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
 
        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) {
                        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++;
                    }
                }
            }
        }
 
        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;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }
 
        private void set(ThreadLocal<?> key, Object value) {
 
            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;
                }
 
                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 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;
                }
            }
        }
 
       
        private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;
            int slotToExpunge = staleSlot;
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;
 
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
 
                if (k == key) {
                    e.value = value;
 
                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;
 
                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }
 
                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }
 
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);
 
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }
 
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
 
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;
 
            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;
 
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }
 
        private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
                i = nextIndex(i, len);
                Entry e = tab[i];
                if (e != null && e.get() == null) {
                    n = len;
                    removed = true;
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
        }
 
        private void rehash() {
            expungeStaleEntries();
 
            if (size >= threshold - threshold / 4)
                resize();
        }
 
        private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;
 
            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }
 
            setThreshold(newLen);
            size = count;
            table = newTab;
        }
 
        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);
            }
        }
    }
}

 內(nèi)部方法:

ThreadLocal通過threadLocalHashCode來標(biāo)識每一個ThreadLocal的唯一性。threadLocalHashCode通過CAS操作進(jìn)行更新,每次hash操作的增量為 0x61c88647(不知為何)。

接下來看下ThreadLocal的set、get等相關(guān)主要方法

set方法:

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

通過Thread.currentThread()方法獲取了當(dāng)前的線程引用,并傳給了getMap(Thread)方法獲取一個ThreadLocalMap的實例。我們繼續(xù)跟進(jìn)getMap(Thread)方法:

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

可以看到getMap(Thread)方法直接返回Thread實例的成員變量threadLocals。它的定義在Thread內(nèi)部,訪問級別為package級別:

public class Thread implements Runnable {
    private static native void registerNatives();
    static {
        registerNatives();
    }
 
    private volatile char  name[];
    private int            priority;
    private Thread         threadQ;
    private long           eetop;
    private boolean     single_step;
    private boolean     daemon = false;
    private boolean     stillborn = false;
    private Runnable target;
    private ThreadGroup group;
    private ClassLoader contextClassLoader;
    private AccessControlContext inheritedAccessControlContext;
    private static int threadInitNumber;
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }
 
    ThreadLocal.ThreadLocalMap threadLocals = null;
 
    ..........
 
}

到了這里,我們可以看出,每個Thread里面都有一個ThreadLocal.ThreadLocalMap成員變量,也就是說每個線程通過ThreadLocal.ThreadLocalMap與ThreadLocal相綁定,這樣可以確保每個線程訪問到的thread-local variable都是本線程的。

我們往下繼續(xù)分析。獲取了ThreadLocalMap實例以后,如果它不為空則調(diào)用ThreadLocalMap.ThreadLocalMap 的set方法設(shè)值;若為空則調(diào)用ThreadLocal 的createMap方法new一個ThreadLocalMap實例并賦給Thread.threadLocals。

ThreadLocal 的 createMap方法的源碼如下:

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

而ThreadLocalMap是ThreadLocal的一個靜態(tài)內(nèi)部類,在文章開頭貼出的ThreadLocal源碼可查看。

總結(jié):

set操作是向當(dāng)前線程的ThreadLocal.ThreadLocalMap類型的成員變量threadLocals中設(shè)置值,key是this,value是我們指定的值

注意,這里傳的this代表的是那個ThreadLocal類型的變量(或者說叫對象)

也就是說,每個線程都維護(hù)了一個ThreadLocal.ThreadLocalMap類型的對象,而set操作其實就是以ThreadLocal變量為key,以我們指定的值為value,最后將這個鍵值對封裝成Entry對象放到該線程的ThreadLocal.ThreadLocalMap對象中。每個ThreadLocal變量在該線程中都是ThreadLocal.ThreadLocalMap對象中的一個Entry。既然每個ThreadLocal變量都對應(yīng)ThreadLocal.ThreadLocalMap中的一個元素,那么就可以對這些元素進(jìn)行讀寫刪除操作。

get方法

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();
    }

通過Thread.currentThread()方法獲取了當(dāng)前的線程引用,并傳給了getMap(Thread)方法獲取一個ThreadLocalMap的實例,getMap方法前面已經(jīng)貼出來了。繼續(xù)跟進(jìn)setInitialValue()方法:

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;
    }

首先調(diào)用 initialValue()方法來初始化,然后 通過Thread.currentThread()方法獲取了當(dāng)前的線程引用,并傳給了getMap(Thread)方法獲取一個ThreadLocalMap的實例,并將 初始化值存到ThreadLocalMap 中。

initialValue() 源碼如下:

protected T initialValue() {
        return null;
    }

總結(jié):

get()方法就是從當(dāng)前線程的ThreadLocal.ThreadLocalMap對象中取出對應(yīng)的ThreadLocal變量所對應(yīng)的值

同理,remove()方法就是清除這個值

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

ThreadLocal的圖形理解:

 或者

 ThreadLocal的使用場景是在線程的聲明周期內(nèi)傳值(數(shù)據(jù)庫連接、session管理等),ThreadLocal關(guān)鍵點是在于ThreadLocalMap,可以說一切歸功于此,看完上面的描述,應(yīng)該會有一個直觀的體會吧。

下面我們探究一下ThreadLocalMap的實現(xiàn)。

ThreadLocalMap

ThreadLocalMap是ThreadLocal的靜態(tài)內(nèi)部類,部分源碼如下:

public class ThreadLocal<T> {
 
    static class 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 initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;
 
        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;
 
        /**
         * The number of entries in the table.
         */
        private int size = 0;
 
        /**
         * The next size value at which to resize.
         */
        private int threshold; // Default to 0
 
        ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
    }
    
}

其中INITIAL_CAPACITY代表這個Map的初始容量;1是一個Entry類型的數(shù)組,用于存儲數(shù)據(jù);size代表表中的存儲數(shù)目;threshold代表需要擴(kuò)容時對應(yīng)size的閾值。

Entry類是ThreadLocalMap的靜態(tài)內(nèi)部類,用于存儲數(shù)據(jù)。

Entry類繼承了WeakReference<ThreadLocal<?>>,即每個Entry對象都有一個ThreadLocal的弱引用(作為key),這是為了防止內(nèi)存泄露。一旦線程結(jié)束,key變?yōu)橐粋€不可達(dá)的對象,這個Entry就可以被GC了。

接下來我們來看ThreadLocalMap 的set方法的實現(xiàn):

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;
                }
 
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
 
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

ThreadLocal 的get方法會調(diào)用 ThreadLocalMap 的 getEntry(ThreadLocal key) ,其源碼如下:

 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;
            if (k == null)
                expungeStaleEntry(i);
            else
                i = nextIndex(i, len);
            e = tab[i];
        }
        return null;
    }

ThreadLocal弱引用

說ThreadLocal是一個弱引用,其本質(zhì)是ThreadLocal類中ThreadLocalMap類中的Entry的key是一個弱引用。前面提到過Entry中的key是this,this指向ThreadLocal

在ThreadLocal源碼中,截取一段ThreadLocalMap的源碼如下:

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
 
            Entry(ThreadLocal<?> k, Object v) {
                //由于Entry繼承了WeakReference,所以這里以一個弱引用指向ThreadLcoal對象
                super(k);
                value = v;
            }
        }

為什么要這么做呢?

看下面的這種場景:

public void func1() {
        ThreadLocal tl = new ThreadLocal<Integer>(); //line1
         tl.set(100);   //line2
         tl.get();       //line3
}

line1新建了一個ThreadLocal對象,t1 是強引用指向這個對象;line2調(diào)用set()后,新建一個Entry,通過源碼可知entry對象里的 k是弱引用指向這個對象。如圖:

當(dāng)func1方法執(zhí)行完畢后,棧幀銷毀,強引用 tl 也就沒有了,但此時線程的ThreadLocalMap里某個entry的 k 引用還指向這個對象。若這個k 引用是強引用,就會導(dǎo)致k指向的ThreadLocal對象及v指向的對象不能被gc回收,造成內(nèi)存泄漏,但是弱引用就不會有這個問題(弱引用及強引用等這里不說了)。使用弱引用,就可以使ThreadLocal對象在方法執(zhí)行完畢后順利被回收,而且在entry的k引用為null后,再調(diào)用get,set或remove方法時,就會嘗試刪除key為null的entry,可以釋放value對象所占用的內(nèi)存。

概括說就是:在方法中新建一個ThreadLocal對象,就有一個強引用指向它,在調(diào)用set()后,線程的ThreadLocalMap對象里的Entry對象又有一個引用 k 指向它。如果后面這個引用 k 是強引用就會使方法執(zhí)行完,棧幀中的強引用銷毀了,對象還不能回收,造成嚴(yán)重的內(nèi)存泄露。

注意:雖然弱引用,保證了k指向的ThreadLocal對象能被及時回收,但是v指向的value對象是需要ThreadLocalMap調(diào)用get、set時發(fā)現(xiàn)k為null時才會去回收整個entry、value,因此弱引用不能保證內(nèi)存完全不泄露。我們要在不使用某個ThreadLocal對象后,手動調(diào)用remoev方法來刪除它,尤其是在線程池中,不僅僅是內(nèi)存泄露的問題,因為線程池中的線程是重復(fù)使用的,意味著這個線程的ThreadLocalMap對象也是重復(fù)使用的,如果我們不手動調(diào)用remove方法,那么后面的線程就有可能獲取到上個線程遺留下來的value值,造成bug。

到此這篇關(guān)于Java中的ThreadLocal線程池原理的文章就介紹到這了,更多相關(guān)ThreadLocal線程池內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot3-yaml文件配置方式

    SpringBoot3-yaml文件配置方式

    這篇文章主要介紹了SpringBoot3-yaml文件配置方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • JavaWeb之Servlet注冊頁面的實現(xiàn)示例

    JavaWeb之Servlet注冊頁面的實現(xiàn)示例

    注冊頁面是很多網(wǎng)站都會是使用的到,本文主要介紹了JavaWeb之Servlet注冊頁面的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • JDK8中Optional類巧用之判空操作

    JDK8中Optional類巧用之判空操作

    善用Optional可以使我們代碼中很多繁瑣、丑陋的設(shè)計變得十分優(yōu)雅,這篇文章主要給大家介紹了JDK8中Optional類巧用之判空的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2021-08-08
  • Java17中record替代Lombok部分功能使用場景探究

    Java17中record替代Lombok部分功能使用場景探究

    這篇文章主要介紹了使用Java17中的record替代Lombok的部分功能,本文來為大家小小的總結(jié)下,我們可以在哪些地方,利用record來替換Lombok
    2024-01-01
  • 詳述IntelliJ IDEA 中自動生成 serialVersionUID 的方法(圖文)

    詳述IntelliJ IDEA 中自動生成 serialVersionUID 的方法(圖文)

    本篇文章主要介紹了詳述IntelliJ IDEA 中自動生成 serialVersionUID 的方法(圖文),具有一定的參考價值,感興趣的小伙伴們可以參考一下。
    2017-11-11
  • Mybatis配置之typeAlias標(biāo)簽的用法

    Mybatis配置之typeAlias標(biāo)簽的用法

    這篇文章主要介紹了Mybatis配置之typeAlias標(biāo)簽的用法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Java如何實現(xiàn)驗證碼驗證功能

    Java如何實現(xiàn)驗證碼驗證功能

    這篇文章主要教大家如何實現(xiàn)Java驗證碼驗證功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-02-02
  • java中public class與class的區(qū)別詳解

    java中public class與class的區(qū)別詳解

    以下是對java中public class與class的區(qū)別進(jìn)行了分析介紹,需要的朋友可以過來參考下
    2013-07-07
  • Java基礎(chǔ)類庫之StringBuffer類用法詳解

    Java基礎(chǔ)類庫之StringBuffer類用法詳解

    String類是在所有開發(fā)項目開發(fā)之中一定會使用的一個功能類。雖然String類很好用,但也有弊端——內(nèi)容不允許頻繁修改,所以為了解決問題,我們提供了StringBuffer類。本文就來講講StringBuffer類的用法
    2022-07-07
  • java中staticclass靜態(tài)類詳解

    java中staticclass靜態(tài)類詳解

    這篇文章主要介紹了java中staticclass靜態(tài)類詳解,具有一定借鑒價值,需要的朋友可以了解下。
    2017-12-12

最新評論