• 类ThreadLocal的使用与源码分析


      变量值的共享可以使用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<String> t1 = new ThreadLocal<String>();
        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<String> t1 = new ThreadLocal<String>();
        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<T> extends ThreadLocal<String> {
        public static Demo3<String> t1 = new Demo3<String>();
        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<String> t2 = new ThreadLocal<String>();
    
        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 <T>
     */
    public class Demo4<T> extends InheritableThreadLocal<String> {
        public static Demo4<String> t1 = new Demo4<String>();
        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 <T>
     */
    public class Demo5<T> extends InheritableThreadLocal<String> {
        public static Demo5<String> t1 = new Demo5<String>();
        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 <T>
     */
    public class Demo6<T> extends InheritableThreadLocal<String> {
        public static Demo6<String> t1 = new Demo6<String>();
        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<ThreadLocal>",有可能这个 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;
        }
  • 相关阅读:
    redis windows下安装
    Plupload 多实例上传 测试可用
    Plupload 上传详细讲解,Plupload 多实例上传,Plupload多个上传按钮--推荐使用
    Class 'AppHttpControllersDB' not found and I also cannot use a new Model
    laravel 控制器类DB类操作
    php-memcached详解
    PHP电商订单自动确认收货redis队列
    写一个PHP函数,实现扫描并打印出指定目录下(含子目录)的所有jpg文件名
    thinkphp5网站中集成使用支付宝手机支付接口
    Memcache服务搭建
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/10125340.html
Copyright © 2020-2023  润新知