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

深入淺出解析Java ThreadLocal原理

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

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

1.了解ThreadLocal

簡(jiǎn)介

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

在這里插入圖片描述

使用

  • (是線程安全的) 在這個(gè)demo中,localStr是共享的,隨后在每個(gè)線程中給localStr設(shè)置值為自己線程的名字,然后再將當(dāng)前線程的日志輸出。
  • sleep5毫秒是為了體現(xiàn)出是否存在線程安全問(wèn)題。
  • 從運(yùn)行結(jié)果可以看到,是不存在線程安全問(wèn)題的:
/**
 * @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ì)于普通變量來(lái)說(shuō),很明顯是存在線程安全問(wèn)題的:

/**
 * @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類之外沒(méi)有暴露任何的操作。這個(gè)類是私有的,允許在類線程中聲明字段。為了處理非常大并長(zhǎng)期存在(對(duì)象)的用法,哈希表的entries使用weakReference作為鍵。但是,由于沒(méi)有使用引用隊(duì)列,因此只有當(dāng)表開始耗盡空間時(shí),才能保證刪除過(guò)時(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英語(yǔ)翻譯一下就是可繼承的ThreadLocal,讓我們看下它和ThreadLocal的繼承性體現(xiàn)在哪。

這里的繼承性指的是:子線程是否能訪問(wè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("訪問(wèn)localStr : " + localStr.get());
            }
        }).start();
        System.out.println(localStr.get());
    }
}

在這里插入圖片描述

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

看一下InheritableThreadLocal的源碼:

在這里插入圖片描述

源碼非常簡(jiǎn)短,下面簡(jiǎn)單分析一下:

  • 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,而不再是原來(lái)的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)建出來(lái),當(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è)繼承性

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

那么當(dāng)子進(jìn)程被創(chuàng)建出來(lái)之后,父進(jìn)程又修改了inheritThreadLocals里面的值,這個(gè)操作是否對(duì)子線程可見,通過(guò)上面的源碼可知,這個(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("子線程第一次訪問(wèn)inheritableLocalStr : " + inheritableLocalStr.get());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("子線程第二次訪問(wèn)inheritableLocalStr : " + inheritableLocalStr.get());
            }
        }).start();
        Thread.sleep(500);
        inheritableLocalStr.set("main線程第二次為inheritableLocalStr設(shè)置的值");
        log.debug("main線程第二次為inheritableLocalStr賦值");
        Thread.sleep(1000);
    }
}

看下輸出:

在這里插入圖片描述

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

總結(jié)

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

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

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

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

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

在這里插入圖片描述

使用強(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í)候,如果沒(méi)有手動(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ì)被使用了,如果沒(méi)有手動(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è)過(guò)時(shí)的entry中,然后返回。

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

總結(jié)

  • ThreadLocal如果對(duì)ThreadLocalMap的key使用強(qiáng)引用,那么會(huì)存在整個(gè)entry發(fā)生內(nèi)存泄露的問(wèn)題,如果不手動(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ù)存放到線程本地,解決了線程安全問(wèn)題,沒(méi)有使用鎖,直接訪問(wèn)線程本地變量,效率較高(空間換時(shí)間。)
同時(shí)threadLocals的生命周期是和線程一致的,可以解決很多參數(shù)傳遞問(wèn)題。

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

ATFWUS 2021-11-11

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

相關(guān)文章

  • Java通過(guò)IO流輸出文件目錄的實(shí)例代碼

    Java通過(guò)IO流輸出文件目錄的實(shí)例代碼

    這篇文章主要介紹了Java通過(guò)IO流輸出文件目錄,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-12-12
  • Spring Boot自定義錯(cuò)誤視圖的方法詳解

    Spring Boot自定義錯(cuò)誤視圖的方法詳解

    這篇文章主要介紹了Spring Boot自定義錯(cuò)誤視圖的方法詳解,需要的朋友可以參考下
    2020-08-08
  • JDBC操作數(shù)據(jù)庫(kù)的增加、刪除、更新、查找實(shí)例分析

    JDBC操作數(shù)據(jù)庫(kù)的增加、刪除、更新、查找實(shí)例分析

    這篇文章主要介紹了JDBC操作數(shù)據(jù)庫(kù)的增加、刪除、更新、查找方法,以完整實(shí)例形式分析了Java基于JDBC連接數(shù)據(jù)庫(kù)及進(jìn)行數(shù)據(jù)的增刪改查等技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-10-10
  • Netty分布式ByteBuf的分類方式源碼解析

    Netty分布式ByteBuf的分類方式源碼解析

    這篇文章主要為大家介紹了Netty分布式ByteBuf的分類方式源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-03-03
  • SpringBoot使用Apache POI庫(kù)讀取Excel文件的操作詳解

    SpringBoot使用Apache POI庫(kù)讀取Excel文件的操作詳解

    在日常開發(fā)中,我們經(jīng)常需要處理Excel文件中的數(shù)據(jù),無(wú)論是從數(shù)據(jù)庫(kù)導(dǎo)入數(shù)據(jù)、處理數(shù)據(jù)報(bào)表,還是批量生成數(shù)據(jù),都可能會(huì)遇到需要讀取和操作Excel文件的場(chǎng)景,本文將詳細(xì)介紹如何使用Java中的Apache POI庫(kù)來(lái)讀取Excel文件,需要的朋友可以參考下
    2025-01-01
  • Go Java 算法之迷你語(yǔ)法分析器示例詳解

    Go Java 算法之迷你語(yǔ)法分析器示例詳解

    這篇文章主要為大家介紹了Go Java 算法之迷你語(yǔ)法分析器示例詳解,
    有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • 探討Java語(yǔ)言中那些修飾符

    探討Java語(yǔ)言中那些修飾符

    在java中常見的修飾符有:abstract(抽象的)、static(靜態(tài)的)、public(公共的)、protected(受保護(hù)的)、private(私有的)、synchronized(同步的)、native(本地的)、transient(暫時(shí)的)、volatile(易失的)、final(不可改變的)。對(duì)java語(yǔ)言修飾符感興趣的朋友一起看看吧
    2016-11-11
  • Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(6)

    Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(6)

    下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,希望可以幫到你
    2021-07-07
  • Java程序?qū)崿F(xiàn)導(dǎo)出Excel的方法(支持IE低版本)

    Java程序?qū)崿F(xiàn)導(dǎo)出Excel的方法(支持IE低版本)

    下面小編就為大家?guī)?lái)一篇Java程序?qū)崿F(xiàn)導(dǎo)出Excel的方法(支持IE低版本)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-07-07
  • 利用SpringMVC接收復(fù)雜對(duì)象和多個(gè)文件(前端使用JQuery)

    利用SpringMVC接收復(fù)雜對(duì)象和多個(gè)文件(前端使用JQuery)

    這篇文章主要介紹了利用SpringMVC接收復(fù)雜對(duì)象和多個(gè)文件(前端使用JQuery),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-10-10

最新評(píng)論

肇州县| 巫山县| 军事| 朝阳县| 玉林市| 南昌市| 梅州市| 台山市| 施秉县| 贺兰县| 弥勒县| 临高县| 旅游| 安阳县| 宜春市| 娱乐| 来凤县| 云浮市| 唐河县| 东平县| 长葛市| 泗水县| 八宿县| 浮梁县| 香河县| 农安县| 灌阳县| 凤城市| 武强县| 佛坪县| 柘城县| 东海县| 青岛市| 哈尔滨市| 博客| 玉龙| 东山县| 铅山县| 呼图壁县| 开平市| 同仁县|