threadlocal应用及其源码分析

ThreadLocal是什么?

简单来讲就是线程局部变量,线程之间操作的变量互不影响各自独立,但如果ThreadLocal存放的是一个全局共享的引用变量依旧会有线程安全问题,ThreadLocal只是将共享变量变成私有变量达到线程安全而已

ThreadLocal的使用

public class ThreadLocalTest01 {

    private final static ThreadLocal<Integer> reqId = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };
    public static void main(String[] args) throws InterruptedException {
        Thread th1 = new Thread(()->{
            reqId.set(1);
            System.out.println("th1:"+reqId.get());
        });
        Thread th2 = new Thread(()->{
            //reqId.set(2);
            System.out.println("th2:"+reqId.get());
        });
        th1.start();
        th2.start();
    }
}

输出结果:

th1:1
th2:0

我们可以看到线程th1和线程th2reqId变量的修改是相互独立的,互不影响

ThreadLocal源码分析

  • get()源码
   public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    
    
  • 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);
}
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

public class Thread implements Runnable {
            ***省略其他代码
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
           ***省略其他代码
}

我们可以看到Thread对象中维护了一个ThreadLocal.ThreadLocalMap的变量,其实就是一个类似HashMap的集合对象,每次get或者set都是对这个容器进行操作,使用ThreadLocal作为key,我们要存的变量作为value

其实就一句话,因为每个线程对象里面都有一个ThreadLocalMap对象,使用ThreadLocal作为key进行存储或者查询,这也就是线程局部变量的由来,操作的是线程对象里面的属性

ThreadLocal在线程池中的使用注意事项

我们将前面的那个例子换成使用线程池去执行看看效果

public class ThreadLocalTest02 {
    private final static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    private final static ExecutorService executor = Executors.newFixedThreadPool(3);
    public static void main(String[] args) throws InterruptedException {
        while (true){
            executor.submit(()->{
               threadLocal.set(1);    //01
                System.out.println("t1 get:"+threadLocal.get());
                //threadLocal.remove(); //03
            });
            Thread.sleep(100);
            executor.submit(() -> {
                //threadLocal.set(1);
                System.out.println("t2 get:" + threadLocal.get());//02
            });
        }
    }
}

输出结果:

t1 get:1
t2 get:null
t1 get:1
t2 get:1
t1 get:1
t1 get:1
t2 get:1
t2 get:1

我们会发现使用线程池后,线程t2会读到线程t1的局部变量。原因很简单那是因为线程池中的线程会复用,我们提交的两个任务,在对于线程池而言很可能会被同一个线程执行,假设线程池中的线程t1执行完后01行代码后,这时在处理新任务02时,就会出现这种错误现象。

解决办法:每次set()完后记得使用remove()清除局部变量即03行注释去掉

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值