深入理解ThreadLocal

深入理解ThreadLocal

ThreadLocal 主要用来提供线程局部变量,与线程同步不同,线程同步是多个线程共享同一个变量,而ThreadLocal是为每一个线程维护一个线程的副本。


ThreadLocal定义了四个方法:
get():返回此线程局部变量的当前线程副本中的值。
initialValue():返回此线程局部变量的当前线程的“初始值”。
remove():移除此线程局部变量当前线程的值。
set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。
案例:

/**
 * Created by Administrator on 2018/3/19.
 */
public class ThreadLocalTest {
    private static ThreadLocal<Integer> threadlocal = new ThreadLocal<Integer>() {
        // 实现initialValue()
        public Integer initialValue() {
            return 0;
        }
    };

    public int nextValue() {
        threadlocal.set(threadlocal.get() + 1);

        return threadlocal.get();
    }

    public static void main(String[] args) {
        ThreadLocalTest threadLocalTest = new ThreadLocalTest();

        ThreadlocalThread thread1 = new ThreadlocalThread(threadLocalTest);
        ThreadlocalThread thread2 = new ThreadlocalThread(threadLocalTest);
        ThreadlocalThread thread3 = new ThreadlocalThread(threadLocalTest);


        thread1.start();
        thread2.start();
        thread3.start();
    }

    private static class ThreadlocalThread extends Thread {
        private ThreadLocalTest threadLocalTest;

        ThreadlocalThread(ThreadLocalTest test) {
            this.threadLocalTest = test;
        }

        public void run() {
            for (int i = 0; i < 3; i++) {
                System.out.println(Thread.currentThread().getName() + " 变量值为 :" + threadLocalTest.nextValue());
            }
        }
    }
}

打印结果:
这里写图片描述


set方法:

//首先获取当前线程,然后调用getMap方法获取属于该线程的map
//如果存在map直接插入value,<K,V> k就是当前Threadlocal的对象引用,v就是插入的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);
    }
//getMap方法:
//threadLocals为线程的一个成员变量,指向当前线程的ThreadLocalMap,默认值为null,也就是说第一次调用Threadlocal的get方法时无法获取到map,需要新创建一个ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
//createMap方法:
//新建一个ThreadLocalMap,然后将当前线程的threadLocals指向新创建map对象
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

通过以上源码我们可以发现,ThreadLocalMap存储的键值对是以ThreadLocal对象的引用为key,然后以变量副本为value。

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的对象引用为key值,从当前线程的ThreadLocalMap中取出该key值对应的Entry,然后得到value值。注意,如果entry不存在则返回ThreadLocal初始化的值,setInitialValue()方法可以被重写,如上例。
ThreadLocalMap结构:

//一个存放变量副本的实体
static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
//存放所有实体的数组
private Entry[] table;
//初始化方法,注意:firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)作为插入数组的位置索引。
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);
        }
//getEntry方法,同样是首先key.threadLocalHashCode & (table.length - 1)获取数组位置,然后取出entry,如果不为空或者key值符合条件,将该实体返回,否则调用getEntryAfterMiss方法
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);
        }
//getEntryAfterMiss方法:通过分析,该方法实际上是继续查找该key值所对应的entry,为什么需要这样?(可能是因为数组的索引可能存在冲突,在ThreadLocalMap中采用的开放定址法去存储,所以该方法即是开放定址法查找元素)
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }
//expungeStaleEntry:该方法主要作用是对key值为空的value值也置空,防止内存泄露。

内存泄露分析:
假如我们将初始化方法重写:

class A
{
}
a=new A();
public A initialValue() {
            return a;
        }

前面提到每个Thread都有一个ThreadLocal.ThreadLocalMap的map,该map的key为ThreadLocal实例,它为一个弱引用,我们知道弱引用有利于GC回收。当ThreadLocal的key == null时,GC就会回收这部分空间,但是value却不一定能够被回收,因为他还与Current Thread存在一个强引用关系,就比我此例中的a对象引用,当key为空的时候,对象a是无法被GC回收的。
所以,在ThreadLocalMap中的setEntry()、getEntry(),如果遇到key == null的情况,会对value设置为null。当然我们也可以显示调用ThreadLocal的remove()方法进行处理。


最后思考一个问题,每一个线程的ThreadLocalMap中能存在多少个元素,因为是以ThreadLocalMap的对象引用作为key,所以每一次set操作是不是仅仅是覆盖value值,不会往map中添加元素?

感谢:http://cmsblogs.com/?p=2442

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值