热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

类ThreadLocal的使用与源码分析

变量值的共享可以使用publicstatic的形式,所有的线程都使用同一个变量。如果每个线程都有自己的共享变量,就可以使用ThreadLocal。比如Hibernat的sessio

  变量值的共享可以使用public static的形式,所有的线程都使用同一个变量。如果每个线程都有自己的共享变量,就可以使用ThreadLocal。比如Hibernat的session问题就是存在ThreadLoca中。

  类ThreadLocal主要解决的就是每个线程绑定自己的值,可以将ThreadLocal比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。

  而且ThreadLocal一般用作静态成员变量封装在工具类中实现线程隔离数据。在JavaEE结构中就是从Action层到Dao层可以使用threadLocal实现共享数据,并且线程之间相互隔离。(对于ThreadLocal,每个线程存进去的东西与取出来的是一致的,不会出现相互覆盖的现象。)

  ThreadLocal就是,把一个数据复制N份,每个线程认领一份,各玩各的,互不影响。

1. 方法 get()与null

   ThreadLocal的基本使用方法。

package cn.qlq.thread.ten;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * ThreadLocal的基本使用
 * 
 * @author QiaoLiQiang
 * @time 2018年12月15日下午9:00:19
 */
public class Demo1 {
    public static ThreadLocal t1 = new ThreadLocal();
    private static final Logger LOGGER = LoggerFactory.getLogger(Demo1.class);

    public static void main(String[] args) {
        if (t1.get() == null) {
            LOGGER.info("从未放过值");
            t1.set("存放的值");
        }
        LOGGER.info("{}", t1.get());
        LOGGER.info("{}", t1.get());
    }
}

结果:

21:02:38 [cn.qlq.thread.ten.Demo1]-[INFO] 从未放过值
21:02:38 [cn.qlq.thread.ten.Demo1]-[INFO] 存放的值
21:02:38 [cn.qlq.thread.ten.Demo1]-[INFO] 存放的值

 

  从第一个的返回结果看,第一次调用t1对象的get()方法时返回的值是null,通过set()赋值之后可以取出值。类ThreadLocal解决的是变量在不同线程间的隔离性,也就是每个线程拥有自己的值,不同线程中的值是可以放入ThreadLocal类中进行保存的。

  

2.验证变量间的隔离性

  验证其隔离性,每个线程在变量间存放的值不同。

package cn.qlq.thread.ten;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * ThreadLocal的基本使用
 * 
 * @author QiaoLiQiang
 * @time 2018年12月15日下午9:00:19
 */
public class Demo2 {
    public static ThreadLocal t1 = new ThreadLocal();
    private static final Logger LOGGER = LoggerFactory.getLogger(Demo2.class);

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (t1.get() == null) {
                    LOGGER.info("从未放过值,threadName->{}", Thread.currentThread().getName());
                    t1.set("存放的值" + Thread.currentThread().getName());
                }
                LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
            }
        }, "thread1").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                if (t1.get() == null) {
                    LOGGER.info("从未放过值,threadName->{}", Thread.currentThread().getName());
                    t1.set("存放的值" + Thread.currentThread().getName());
                }
                LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
            }
        }, "thread2").start();
    }
}

结果: (由结果可以看出每个线程存放了不同的值,但是在获取值的时候,每个线程又获取到了不同的值。)

21:11:08 [cn.qlq.thread.ten.Demo2]-[INFO] 从未放过值,threadName->thread2
21:11:08 [cn.qlq.thread.ten.Demo2]-[INFO] 从未放过值,threadName->thread1
21:11:08 [cn.qlq.thread.ten.Demo2]-[INFO] threadName - >thread2,值->存放的值thread2
21:11:08 [cn.qlq.thread.ten.Demo2]-[INFO] threadName - >thread1,值->存放的值thread1

 

3.解决get()返回null问题

   为了解决返回为null的问题,也就是在get()的时候直接就返回默认值,采用继承ThreadLocal并且重写initialValue的方式实现。(初始值的时候也可以实现线程的隔离线)

package cn.qlq.thread.ten;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 解决get()返回null的问题
 * 
 * @author QiaoLiQiang
 * @time 2018年12月15日下午9:16:17
 */
public class Demo3 extends ThreadLocal {
    public static Demo3 t1 = new Demo3();
    private static final Logger LOGGER = LoggerFactory.getLogger(Demo3.class);

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (t1.get() == null) {
                    LOGGER.info("从未放过值,threadName->{}", Thread.currentThread().getName());
                    t1.set("存放的值" + Thread.currentThread().getName());
                }
                LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
            }
        }, "thread1").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                if (t1.get() == null) {
                    LOGGER.info("从未放过值,threadName->{}", Thread.currentThread().getName());
                    t1.set("存放的值" + Thread.currentThread().getName());
                }
                LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
            }
        }, "thread2").start();
    }

    @Override
    protected String initialValue() {
        return "这是初始值" + Thread.currentThread().getName();
    }
}

结果:

21:23:54 [cn.qlq.thread.ten.Demo3]-[INFO] threadName - >thread2,值->这是初始值thread2
21:23:54 [cn.qlq.thread.ten.Demo3]-[INFO] threadName - >thread1,值->这是初始值thread1

 

4. ThreadLocal中存入多个对象

   有时候我们在ThreadLocal 中希望共享多个变量。

  最简单的一种办法创建一个ThreadLocal就是将所有共享的数据存入一个Map,将Map存入ThreadLocal,另一种办法就是所有共享数据放入一个bean中将bean存入ThreadLocal。

  另一种办法就是每个创建多个ThreadLocal分别存放多种共享的数据。

如下一个ThreadLocal存入Map中实现共享多个数据

package cn.qlq.thread.ten;

import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@SuppressWarnings("all")
public class Demo7 {
    public static ThreadLocal t1 = new ThreadLocal();

    private static final Logger LOGGER = LoggerFactory.getLogger(Demo7.class);

    public static void main(String[] args) throws InterruptedException {
        Map data = new HashMap();
        data.put("str", "111222");
        data.put("int", 11122);
        data.put("obj", new Object());
        t1.set(data);
        Object object = t1.get();
        System.out.println(object);
    }
}

结果:

{str=111222, int=11122, obj=java.lang.Object@77700f3d}

 

或者多个ThreadLocal共享多个数据

package cn.qlq.thread.ten;

import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@SuppressWarnings("all")
public class Demo7 {
    public static ThreadLocal t1 = new ThreadLocal();
    public static ThreadLocal t2 = new ThreadLocal();

    private static final Logger LOGGER = LoggerFactory.getLogger(Demo7.class);

    public static void main(String[] args) throws InterruptedException {
        Map data = new HashMap();
        data.put("str", "111222");
        data.put("int", 11122);
        data.put("obj", new Object());
        t1.set(data);

        t2.set("t2");

        System.out.println(t1.get());
        System.out.println(t2.get());
    }
}

结果:

{str=111222, int=11122, obj=java.lang.Object@271455a2}
t2

 

 5. 类InheritableThreadLocal的使用

5.1值继承

   使用InheritableThreadLocal可以在子线程从父线程中继承值。主线程存入值,在子线程中获取。

package cn.qlq.thread.ten;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 主线程中设置值,子线程中获取值
 * 
 * @author QiaoLiQiang
 * @time 2018年12月15日下午9:29:40
 * @param 
 */
public class Demo4 extends InheritableThreadLocal {
    public static Demo4 t1 = new Demo4();
    private static final Logger LOGGER = LoggerFactory.getLogger(Demo4.class);

    public static void main(String[] args) {
        // 主线程中存入值
        t1.set("存放的值" + Thread.currentThread().getName());

        new Thread(new Runnable() {
            @Override
            public void run() {
                if (t1.get() == null) {
                    LOGGER.info("从未放过值,threadName->{}", Thread.currentThread().getName());
                    t1.set("存放的值" + Thread.currentThread().getName());
                }
                LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
            }
        }, "thread1").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                if (t1.get() == null) {
                    LOGGER.info("从未放过值,threadName->{}", Thread.currentThread().getName());
                    t1.set("存放的值" + Thread.currentThread().getName());
                }
                LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
            }
        }, "thread2").start();
    }
}

 结果:

21:29:02 [cn.qlq.thread.ten.Demo4]-[INFO] threadName - >thread2,值->存放的值main
21:29:02 [cn.qlq.thread.ten.Demo4]-[INFO] threadName - >thread1,值->存放的值main

 

测试在子线程中再次创建子线程。(值会一直继承下去,对自己的子线程创建的子线程也有效

package cn.qlq.thread.ten;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 主线程中设置值,子线程中获取值
 * 
 * @author QiaoLiQiang
 * @time 2018年12月15日下午9:29:40
 * @param 
 */
public class Demo5 extends InheritableThreadLocal {
    public static Demo5 t1 = new Demo5();
    private static final Logger LOGGER = LoggerFactory.getLogger(Demo5.class);

    public static void main(String[] args) {
        // 主线程中存入值
        t1.set("存放的值" + Thread.currentThread().getName());

        // 创建子线程获取值
        new Thread(new Runnable() {
            @Override
            public void run() {
                LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());

                // 创建子子线程获取值
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
                    }
                }, "thread2").start();
            }
        }, "thread1").start();
    }
}

结果:

21:32:34 [cn.qlq.thread.ten.Demo5]-[INFO] threadName - >thread1,值->存放的值main
21:32:34 [cn.qlq.thread.ten.Demo5]-[INFO] threadName - >thread2,值->存放的值main

 

5.2 值继承再修改

  值也可以被继承再修改。

package cn.qlq.thread.ten;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 继承再修改值
 * 
 * @author QiaoLiQiang
 * @time 2018年12月15日下午9:34:41
 * @param 
 */
public class Demo6 extends InheritableThreadLocal {
    public static Demo6 t1 = new Demo6();
    private static final Logger LOGGER = LoggerFactory.getLogger(Demo6.class);

    public static void main(String[] args) throws InterruptedException {
        // 主线程中存入值
        t1.set("存放的值" + Thread.currentThread().getName());
        LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());

        // 创建子线程获取值
        new Thread(new Runnable() {
            @Override
            public void run() {
                LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());

                // 主线程中存入值
                t1.set("存放的值" + Thread.currentThread().getName());
                LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());

                // 创建子子线程获取值
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
                    }
                }, "thread2").start();
            }
        }, "thread1").start();

        Thread.sleep(2 * 1000);
        LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get());
    }
}

结果:(主线程中存入值,在子线程修改了值,main线程取到的值还是main中存入的值,子线程以及子子线程获得的值是子线程修改的值。)

21:36:26 [cn.qlq.thread.ten.Demo6]-[INFO] threadName - >main,值->存放的值main
21:36:26 [cn.qlq.thread.ten.Demo6]-[INFO] threadName - >thread1,值->存放的值main
21:36:26 [cn.qlq.thread.ten.Demo6]-[INFO] threadName - >thread1,值->存放的值thread1
21:36:26 [cn.qlq.thread.ten.Demo6]-[INFO] threadName - >thread2,值->存放的值thread1
21:36:28 [cn.qlq.thread.ten.Demo6]-[INFO] threadName - >main,值->存放的值main

 

6.ThreadLocal 源码解析

  ThreadLocal其实比较简单,因为类里就三个public方法:set(T value)、get()、remove()。

三个理论基础:

1、每个线程都有一个自己的ThreadLocal.ThreadLocalMap对象 (ThreadLocalMap 是ThreadLocal的静态内部类,一个类似于Map结构的普通类,没有实现Map接口,也是内部维护一个静态内部类Entry存放数据,而且其内部的Entry继承 WeakReference弱引用(被若引用关联的对象只能生存到下一次垃圾回收之前。其内部的key是Threadlocal,value就是存入的值)。)

Thread.class中的一个成员属性:

    ThreadLocal.ThreadLocalMap threadLocals = null;

2、每一个ThreadLocal对象都有一个循环计数器
3、ThreadLocal.get()取值,就是根据当前的线程,获取线程中自己的ThreadLocal.ThreadLocalMap,然后在这个Map中根据第二点中循环计数器取得一个特定value值

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

 

   可以看出是先获取到当前线程,然后根据当前线程去获取  ThreadLocalMap ,如果 获取到的ThreadLocalMap不为空的话就直接set值,否则走  createMap方法创建map。

 

(1)getmap(t)从线程中获取   ThreadLocalMap    (上面说过了每个Thread都有都有一个自己的ThreadLocal.ThreadLocalMap对象)

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

 

(2)map.set(this,value)设置值

        /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        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();
        }
    /**
     * Returns the next hash code.
     */
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

 

    private final int threadLocalHashCode = nextHashCode();

    /**
     * The next hash code to be given out. Updated atomically. Starts at
     * zero.
     */
    private static AtomicInteger nextHashCode =
        new AtomicInteger();

  这个Map存储的方式不是链表法而是开地址法。看到设置table中的位置的时候,都把一个static的nextHashCode累加一下,这意味着,set的同一个value,可能在每个ThreadLocal.ThreadLocalMap中的table中的位置都不一样。

 

  1. 先对ThreadLocal里面的threadLocalHashCode取模获取到一个table中的位置
  2. 这个位置上如果有数据,获取这个位置上的ThreadLocal

  (1)判断一下位置上的ThreadLocal和我本身这个ThreadLocal是不是一个ThreadLocal,是的话数据就覆盖,返回

  (2)不是同一个ThreadLocal,再判断一下位置上的ThreadLocal是是不是空的,这个解释一下。Entry是ThreadLocalMap的一个静态内部类,并且是弱引用,"static class Entry extends WeakReference",有可能这个 Entry 被垃圾回收了,这时候把新设置的value替换到当前位置上,返回

  (3)上面都没有返回,给模加1,看看模加1后的table位置上是不是空的,是空的再加1,判断位置上是不是空的...一直到找到一个table上的位置不是空的为止,往这里面塞一个value。换句话说,当table的位置上有数据的时候,ThreadLocal采取的是办法是找最近的一个空的位置设置数据

        3.这个位置上如果没有数据,就创建一个Entry到这个位置。

 

(3)   createMap(thread,value)方法查看:

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
        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);
        }

 

创建一个ThreadLocalMap并且将引用传递给线程对象的 threadLocals 。

ThreadLocalMap创建的时候做了一些初始化工作,并且将值设置进去。

 

6.2  get()源码解读

get()方法的源码如下:

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

 

获取到当前线程-》获取到当前线程里面的ThreadLocalMap -》如果ThreadLocalMap 为不为null,获取其内部的Entry对象-》获取entry的value(根据ThreadLocal 获取一个下标,然后获取对应下标的entry的信息)

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

 

如果ThreadLocalMap 为null,做默认设置并且返回默认值(这也是我们在上面的例子中继承ThreadLocal重写initialValue方法可以设置默认值的原因)

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

 

6.3  remove()源码解读

remove()源码如下:

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
        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;
                }
            }
        }

根据当前线程获取到ThreadLocalMap-》如果获取的ThreadLocalMap不为null,调用其remove(key)方法

remove(ThreadLocal)根据ThreadLocal 对象获取一个下标i,如果tab[i]不为null,即存在对应的entry,调用entry的clear方法

 

补充:clear方法是Reference类型一个方法:--其作用就是将referent置为null,垃圾回收就可以回收此对象。

    public void clear() {
        this.referent = null;
    }

补充:   每个线程都维护了一个ThreadLocal.ThreadLocalMap类型的对象,而set操作其实就是以ThreadLocal变量为key,以我们指定的值为value,最后将这个键值对封装成Entry对象放到该线程的ThreadLocal.ThreadLocalMap对象中。每个ThreadLocal变量在该线程中都是ThreadLocal.ThreadLocalMap对象中的一个Entry。既然每个ThreadLocal变量都对应ThreadLocal.ThreadLocalMap中的一个元素,那么就可以对这些元素进行读写删除操作。

补充:ThreadLocal内存泄露

  内存泄露为程序在申请内存后,无法释放已申请的内存空间。通俗的说,就是:不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。

前面了解了java.lang.ThreadLocal.ThreadLocalMap.Entry是一个虚引用,且只针对Key是弱引用:(可以看出来只是将Key虚引用,内部只存了value属性)

        static class Entry extends WeakReference> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

super方法如下: java.lang.ref.Reference#Reference(T)

    Reference(T referent) {
        this(referent, null);
    }

ThreadLocal 内部各种引用关系如下:(虚线代表虚引用)

类ThreadLocal的使用与源码分析

   ThreadLocal自身并不储存值,而是作为一个key来让线程从ThreadLocal获取value。Entry是中的key是弱引用,所以jvm在垃圾回收时如果外部没有强引用来引用它,ThreadLocal必然会被回收。但是,作为ThreadLocalMap的key,ThreadLocal被回收后,ThreadLocalMap就会存在null,但value不为null的Entry。若当前线程一直不结束,可能是作为线程池中的一员,线程结束后不被销毁,或者分配(当前线程又创建了ThreadLocal对象)使用了又不再调用get/set方法,就可能引发内存泄漏。其次,就算线程结束了,操作系统在回收线程或进程的时候不是一定杀死线程或进程的,在繁忙的时候,只会清除线程或进程数据的操作,重复使用线程或进程(线程id可能不变导致内存泄漏)。因此,key弱引用并不是导致内存泄漏的原因,而是因为ThreadLocalMap的生命周期与当前线程一样长,并且没有手动删除对应key。也就是线程池中使用的时候会有内存泄漏的问题。

  为了避免内存泄露,正确的使用方法是:在使用完ThreadLocal时,及时调用它的的remove方法清除数据。

 


推荐阅读
  • Android目录遍历工具 | AppCrawler自动化测试进阶(第二部分):个性化配置详解
    终于迎来了“足不出户也能为社会贡献力量”的时刻,但有追求的测试工程师绝不会让自己的生活变得乏味。与其在家消磨时光,不如利用这段时间深入研究和提升自己的技术能力,特别是对AppCrawler自动化测试工具的个性化配置进行详细探索。这不仅能够提高测试效率,还能为项目带来更多的价值。 ... [详细]
  • Android 图像色彩处理技术详解
    本文详细探讨了 Android 平台上的图像色彩处理技术,重点介绍了如何通过模仿美图秀秀的交互方式,利用 SeekBar 实现对图片颜色的精细调整。文章展示了具体的布局设计和代码实现,帮助开发者更好地理解和应用图像处理技术。 ... [详细]
  • 本题库精选了Java核心知识点的练习题,旨在帮助学习者巩固和检验对Java理论基础的掌握。其中,选择题部分涵盖了访问控制权限等关键概念,例如,Java语言中仅允许子类或同一包内的类访问的访问权限为protected。此外,题库还包括其他重要知识点,如异常处理、多线程、集合框架等,全面覆盖Java编程的核心内容。 ... [详细]
  • 深入解析Spring框架中的双亲委派机制突破方法
    在探讨Spring框架中突破双亲委派机制的方法之前,首先需要了解类加载器的基本概念。类加载器负责将类的全限定名转换为对应的二进制字节流。每个类在被特定的类加载器加载后,其唯一性得到保证。然而,这种机制在某些场景下可能会限制灵活性,因此Spring框架提供了一些策略来突破这一限制,以实现更加动态和灵活的类加载。这些策略不仅能够提升系统的可扩展性,还能在复杂的运行环境中确保类的正确加载和管理。 ... [详细]
  • 第五周教学内容回顾与实验成果分析报告
    在第五周的教学内容回顾与实验成果分析报告中,我们重点探讨了String类的应用。实验旨在使学生熟练掌握String类的各种操作方法,并学会利用JDK帮助文档解决实际问题。具体实验内容包括对给定字符串“thisisatestof”进行多种操作,如字符串分割、拼接、查找子字符串等,以加深对String类功能的理解和应用。通过本次实验,学生们不仅巩固了理论知识,还提升了实际编程能力。 ... [详细]
  • Java 中优先级队列的轮询方法详解与应用 ... [详细]
  • 本文探讨了在Android应用中实现动态滚动文本显示控件的优化方法。通过详细分析焦点管理机制,特别是通过设置返回值为`true`来确保焦点不会被其他控件抢占,从而提升滚动文本的流畅性和用户体验。具体实现中,对`MarqueeText.java`进行了代码层面的优化,增强了控件的稳定性和兼容性。 ... [详细]
  • 如何在 Java LinkedHashMap 中高效地提取首个或末尾的键值对? ... [详细]
  • 如何在Java中高效构建WebService
    本文介绍了如何利用XFire框架在Java中高效构建WebService。XFire是一个轻量级、高性能的Java SOAP框架,能够简化WebService的开发流程。通过结合MyEclipse集成开发环境,开发者可以更便捷地进行项目配置和代码编写,从而提高开发效率。此外,文章还详细探讨了XFire的关键特性和最佳实践,为读者提供了实用的参考。 ... [详细]
  • Go语言实现Redis客户端与服务器的交互机制深入解析
    在前文对Godis v1.0版本的基础功能进行了详细介绍后,本文将重点探讨如何实现客户端与服务器之间的交互机制。通过具体代码实现,使客户端与服务器能够顺利通信,赋予项目实际运行的能力。本文将详细解析Go语言在实现这一过程中的关键技术和实现细节,帮助读者深入了解Redis客户端与服务器的交互原理。 ... [详细]
  • 技术日志:深入探讨Spark Streaming与Spark SQL的融合应用
    技术日志:深入探讨Spark Streaming与Spark SQL的融合应用 ... [详细]
  • 在Spring框架中,基于Schema的异常通知与环绕通知的实现方法具有重要的实践价值。首先,对于异常通知,需要创建一个实现ThrowsAdvice接口的通知类。尽管ThrowsAdvice接口本身不包含任何方法,但开发者需自定义方法来处理异常情况。此外,环绕通知则通过实现MethodInterceptor接口来实现,允许在方法调用前后执行特定逻辑,从而增强功能或进行必要的控制。这两种通知机制的结合使用,能够有效提升应用程序的健壮性和灵活性。 ... [详细]
  • Java 8 引入了 Stream API,这一新特性极大地增强了集合数据的处理能力。通过 Stream API,开发者可以更加高效、简洁地进行集合数据的遍历、过滤和转换操作。本文将详细解析 Stream API 的核心概念和常见用法,帮助读者更好地理解和应用这一强大的工具。 ... [详细]
  • Go语言中的高效排序与搜索算法解析
    在探讨Go语言中高效的排序与搜索算法时,本文深入分析了Go语言提供的内置排序功能及其优化策略。通过实例代码,详细讲解了如何利用Go语言的标准库实现快速、高效的排序和搜索操作,为开发者提供了实用的编程指导。 ... [详细]
  • 如何在Android应用中设计和实现专业的启动欢迎界面(Splash Screen)
    在Android应用开发中,设计与实现一个专业的启动欢迎界面(Splash Screen)至关重要。尽管Android设计指南对使用Splash Screen的态度存在争议,但一个精心设计的启动界面不仅能提升用户体验,还能增强品牌识别度。本文将探讨如何在遵循最佳实践的同时,通过技术手段实现既美观又高效的启动欢迎界面,包括加载动画、过渡效果以及性能优化等方面。 ... [详细]
author-avatar
鑫瑜Twinkle
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有