【java基础】 threadLocal源码分析

先看下面一段代码

public class App {
    private static final ThreadLocal<Integer> tnum = ThreadLocal.withInitial(()->0);
    private static Integer num =0;

    public static void main(String[] args) {
        for(int i=0;i<3;i++){
            new Thread(()->{
                for(int j=0;j<10;j++){
                    try {
                        TimeUnit.MILLISECONDS.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    tnum.set(tnum.get()+1);
                    num++;
                }
                System.out.println(Thread.currentThread().getName()+"---tnum=="+tnum.get());
                System.out.println(Thread.currentThread().getName()+"----num=="+num);
            },"THREAD"+i).start();
        }
    }
}

看下执行结果

THREAD2---tnum==10
THREAD0---tnum==10
THREAD0----num==27
THREAD1---tnum==10
THREAD1----num==27
THREAD2----num==27

不管执行几次,num的值因为线程安全问题一直在变。但是tnum一直都是10,也就是线程循环执行的次数。这也说明了threadLoacl是线程隔离的,每个线程之间相互不影响,那么接着就看下源码


上述代码中实现tnum自增的是tnum.set(tnum.get()+1);  那么就先看下get()方法吧

java.lang.ThreadLocal#get

public T get() {
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取当前线程的ThreadLocalMap类型的属性threadLocals
    ThreadLocal.ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

1.)获取当前线程的ThreadLocalMap类型的属性threadLocals 【java.lang.ThreadLocal#getMap】

ThreadLocal.ThreadLocalMap getMap(Thread t) {
    //这里说明Thread类有一个ThreadLocalMap类型的属性,下面也附有Thread类的部分截图
    return t.threadLocals;
}

2)从ThreadLocalMap中获取内容

private ThreadLocal.ThreadLocalMap.Entry getEntry(ThreadLocal<?> key) {
    //把key的hash值和table的length值与操作
    int i = key.threadLocalHashCode & (table.length - 1);
    //获取对应的值
    ThreadLocal.ThreadLocalMap.Entry e = table[i];
    /**
     *  判断 e.get() == key是因为,与hashMap不同,
     *  ThreadLocalMap的Entry位置的值冲突之后,不做链表或者树化的操作,而是放到Entry的下一个位置
     */
    if (e != null && e.get() == key)
        return e;
    else
        /**
         * 这里基本上就是遍历Entry的数组找到key对应的Entry,顺带做一下啊内存泄漏工作的处理
         */
        return getEntryAfterMiss(key, i, e);
}

/**
 * Entry继承WeakReference,key是弱引用,
 * 一次GC之后key就没了,但是value值还有引用,容易造成内存泄漏
 * 这也是为什么一般ThreadLocal的变量都是用static final来修饰的
 */
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

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

3)如果当前线程的threadLocals为空,则执行setInitialValue()初始化

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocal.ThreadLocalMap map = getMap(t);//同样的先获取map
    if (map != null)//不为空则直接赋值
        map.set(this, value);
    else
        createMap(t, value);//为空,则创建map并赋值
    return value;
}

/**
 * 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.

    ThreadLocal.ThreadLocalMap.Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    //遍历Entry 数组,直到遇到空的,这也说明了,如果槽位值重复了,不做链表,而是放到下一个槽位
    for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {

        ThreadLocal<?> k = e.get();
        //Entry的key等于要添加的key值,value就覆盖原来的值
        if (k == key) {
            e.value = value;
            return;
        }

        //k为空,那就需要处理内存泄漏的问题了
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    
    //此时tab[i]为空,则创建新的Entry对象并放到数组中去
    tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

整个关系图差不多是这个样子,一个线程对应一个map,但是map的entry的key值是ThreadLocal对象,所有key值都是一样的

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值