• ThreadLocal


    1.定义

    线程本地的变量副本,属于每个线程独有。每个线程使用ThreadLocal设置自己的值,设置的值之间不受影响,但是使用同一个ThreadLocal对象。所以设置的每个变量,是给每个线程一个独有的变量副本

    2.简单使用

    public class HelloThreadLocal {
        private static ThreadLocal<Loan> threadLocal = new ThreadLocal<Loan>();
    
        public static void main(String[] args) {
            //线程1 使用threadLocal设置自己的变量副本
            new Thread(() -> {
                threadLocal.set(new Loan("zhangsan", "1000.00"));
                System.out.println("线程-1loan:" + threadLocal.get());
            }).start();
    
            //线程2 使用threadLocal设置自己的变量副本
            new Thread(() -> {
                try {
                    Thread.sleep(1 * 1000);
                } catch (InterruptedException e) {
                }
                HelloThreadLocal.Loan loan = threadLocal.get();
                System.out.println("线程-2loan:" + loan);
                threadLocal.set(new Loan("lisi", "2000.00"));
                loan = threadLocal.get();
                System.out.println("线程-2loan:" + loan);
    
            }).start();
    
            try {
                Thread.sleep(5 * 1000);
            } catch (InterruptedException e) {
            }
    
            System.out.println("main-线程loan:" + threadLocal.get());
            threadLocal.set(new Loan("wangwu", "1000.00"));
            System.out.println("main-线程loan:" + threadLocal.get());
    
        }
    
        @Data
        @AllArgsConstructor
        public static class Loan {
            private String name;
            private String amount;
        }
    }
    

    输出结果:

    线程-1loan:HelloThreadLocal.Loan(name=zhangsan, amount=1000.00)
    线程-2loan:null
    线程-2loan:HelloThreadLocal.Loan(name=lisi, amount=2000.00)
    main-线程loan:null
    main-线程loan:HelloThreadLocal.Loan(name=wangwu, amount=1000.00)
    

    可以看出,每个线程无法获取到其它线程设置的loan对象

    3.源码分析

    1.get方法

    首先获取当前线程的ThreadLocalMap变量,如果map为空的话调用setInitialValue方法返回默认值,如果map不为空获取entry中key对应的value值

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

    getMap方法:

    threadLocals变量用于存储当前线程自身的ThreadLocal,所以虽然使用的是ThreadLocal的get方法,但是操作的实际是当前线程的threadLocals本地遍历副本的Map

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

    setInitialValue方法:

    首先初始化value为null,然后当前线程获取map,如果为null的话,创建map,否则直接set,这里会返回null,所以get方法也会返回null的值

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

    2.set方法

    如果是第一次使用set方法,创建一个默认大小为16的ThreadLocalMap,并将key设为ThreadLocalMap对象,value用传入的value,如果不是第一次直接map.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);
    }
    
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    

    3.ThreadLocalMap

    ThreadLocalMap中的核心结构是一个Entry,用来存储key-value数据:

    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
    
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    

    这里继承了弱引用,弱引用是只要gc发现了它,就会回收掉,这里的key使用弱引用是为了防止内存泄漏,因为如果key是强引用的话,当一个threadLocal对象为null后,还是会指向它,导致该对象不能被回收,造成内存泄漏,这里虽然key使用了弱引用,但是还是存在value指向的强引用,所以需要用remove方法来删除之前的key,不然还是会造成内存泄漏

    4.应用场景

    1.Spring的Transaction机制中,将一个线程中的事务放入ThreadLocal中,可以在整个方法调用栈中随时取出事务的信息进行操作,不会影响其它线程

    2.Log4j2等日志框架中的MDC

    3.HDFS edits_log的txId自增后放入线程本地副本,HDFS的每条edit_log都有一个txId,会将这个txId记录到当前线程方便在整个线程过程中随时取用

    小结:

    ThreadLocal最常用的2个场景就是:

    1.线程中,在各个方法需要共享变量时使用。除了方法之间传递入参,通过ThreadLocal可以很方便的做到这一点

    2.多线程操作时,防止并发冲突,保证线程安全。比如一般会拷贝一份数据到线程本地,自己修改本地变量,是线程安全的

  • 相关阅读:
    background及background-size
    -ms-,-moz-,-webkit-,-o-含义
    &:first-of-type含义
    ES6的Promise对象
    LightOJ 1029 Civil and Evil Engineer最小生成树和最大生成树
    快速幂模板
    poj2965 The Pilots Brothers' refrigerator 枚举或DFS
    poj1753Flip Game(枚举+DFS)
    POJ 1751Highways
    HDU 1875 畅通工程再续 prim模板题
  • 原文地址:https://www.cnblogs.com/jordan95225/p/13604711.html
Copyright © 2020-2023  润新知