TheadLocal的理解

ThreadLocal是什么?

ThreadLocal是用来维护线程中的变量不被其他线程干扰而出现的一个结构,内部包含一个ThreadLocalMap类,该类为Thread类的一个局部变量,该Map存储的keyThreadLocal对象自身,value为我们要存储的对象,这样一来,在不同线程中,持有的其实是当前线程的变量副本,与其他线程完全隔离,以此来保证线程执行过程中不受其他线程的影响。

ThreadLocal的原理

ThreadLocal有4个public的方法,以及一个内部类ThreadLocalMap,里面存储的是Entry数组。4个公有方法中,一个是初始化用的,另外三个get(),set(),remove()是对数据的处理;操作的数据结构是ThreadLocalMap,我们先来看一段代码

假设如下场景

  1. 我们给每个线程生成一个ID。
  2. 一旦设置,线程生命周期内不可变化。
  3. 容器活动期间不可以生成重复的ID

我们创建一个ThreadLocal管理类

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadLocalSample
{
    public static void main(String[] args) {
        incrementSameThreadId();

        new Thread(new Runnable() {
            @Override
            public void run() {
                incrementSameThreadId();
            }
        },"1").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                incrementSameThreadId();
            }
        },"2").start();
    }


    private static void incrementSameThreadId(){
        try{
            for (int i = 0; i <5; i++) {
                System.out.println(Thread.currentThread()+"_"+i+",threadId:"+ThreadLocalId.get());
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            ThreadLocalId.remove();
        }
    }
}


class ThreadLocalId {

    private static final AtomicInteger nextId = new AtomicInteger(0);

    private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>()
    {
        @Override
        protected Integer initialValue(){//get方法时会调用
            System.out.println(Thread.currentThread()+":initialValue");
            return nextId.getAndIncrement();
        }
    };

    static int get(){
        return threadId.get();
    }

    static void remove(){
        threadId.remove();
    }

}

可以看到,不同线程输出的ThreadLocal保存值是独立,它到底是怎么做到的呢?

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) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

get方法时ThreadLocal的核心方法之一,其核心是根据调用的线程来获取该线程保存的值,我觉得这就是为什么ThreadLocal能在不同线程返回不同的值的原因,也是ThreadLocal为什么天然是线程安全的原因。从调用的线程中找到要返回的值

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

构造的原理和get方法差不多,这里比较清晰地指出ThreadLocalMap中的key值为当前ThreadLocal对象本身(具体来说是经过hash运算的值才是key值)

ThreadLocalMap

可以粗略地理解为是一个hashmap,里面是Entry数组,根据对象的hash值获取下标,把对应的value值存入数组中

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

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
存在内存泄露的问题

可以看到Entry的在构造方法中用到了两个值,一个是ThreadLocal<?> k作为key值,一个是Object v作为value值,Entry本身继承了WeakReference,是一个弱引用,但是这个弱引用只针对key值本身,并没有针对value值。

解决方法,每次调用结束都要显式调用remove方法

总结

为了更好的避免这种情况的发生我们使用ThreadLocal时遵守以下两个小原则:
①ThreadLocal申明为private static final
Private与final 尽可能不让他人修改变更引用,
Static 表示为类属性,只有在程序结束才会被回收。
②ThreadLocal使用后务必调用remove方法。
最简单有效的方法是使用后将其移除。

引用拓展
类型回收时间应用场景
强引用一直存活,除非GC roots不可达所有程序的场景,基本对象,自定义对象等
软引用内存不足时会被回收一般用在对内存非常敏感的资源上,如一些缓存上
弱引用对象没有其他被强引用的话,只能存活到下一次GC前生命周期很短的对象,例如ThreadLocal中的Key。
虚引用随时会被回收, 创建了可能很快就会被回收可能被JVM团队内部用来跟踪JVM的垃圾回收活动

应用

SimpleDateFormat线程安全问题详解

文章引用

ThreadLocal理解及应用(证明了泄露位置)

ThreadLocal是什么?有哪些使用场景

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值