分享

ThreadLocal的源码分析

 碧海山城 2010-10-07

简单总结:

 

一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。

 

总之,ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。归纳了两点:
 1
。每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
 2
。将一个公用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦

一个ThreadLocal只能存放一个对象,网上有人讨论说:

SINCE1978 写道

我想知道如果多次new ThreadLocal并且调用其set方法的话、是否就和普通hashmap一样后set进去的会覆盖先set进去的?这样的话ThreadLocal只能植入一个资源喽?这肯定不对,否则还用ThreadLocalMap这个自定义哈希表干什么,那么如何区分一个线程当中不同方法或不同类set进去的资源?并正确setget??



每个ThreadLocal当然只能放一个对象。要是需要放其他的对象,就再new 一个新的ThreadLocal出来,这个新的ThreadLocal作为key,需要放的对象作为value,放在ThreadLocalMap中。。。。

 

 

 

 

 

 

 

 

 

 


甚至还有人通过某些方式提供了一种一个ThreadLocal存放更丰富的对象比如Map,不用实例化太多thread local的方法,但是看了源码之后,我们会发现

 

初始化多次TheadLocal并没有多大的问题,也没有什么资源的浪费,一些人的误解可能是因为对ThreadLocalMap的误解:以为是一个ThreadLocal对应一个ThreadLocalMap,其实,是一个Thread对应一个ThreadLocalMap,所以在一个线程中创建多个ThredLocal实例的开销只有:set的时候,要将threadLocalHashCode 来计算哈希值,并将其放到线程实例唯一的ThreadLocalMap中,或者说是哈希函数计算后,放到数组中,,其他的开销,都在第一次set的时候就做了,就是创建一个线程唯一的ThreadLocalMap

 

 

 

 

代码:

 

public static ThreadLocal<Monkey> local=new ThreadLocal<Monkey>();

   

Monkey mm=new Monkey();

mm.a=1;

mm.b=2;

local.set(mm);

      

      

Monkey mm2=local.get();

System.out.println(mm2.a);

System.out.println(mm2.b);

 

 

源码解析:

 

/**

     * ThreadLocals rely on per-thread linear-probe hash maps attached

     * to each thread (Thread.threadLocals and

     * inheritableThreadLocals).  The ThreadLocal objects act as keys,

     * searched via threadLocalHashCode.  This is a custom hash code

     * (useful only within ThreadLocalMaps) that eliminates collisions

     * in the common case where consecutively constructed ThreadLocals

     * are used by the same threads, while remaining well-behaved in

     * less common cases.

     */

    private final int threadLocalHashCode = nextHashCode();

 

    private static AtomicInteger nextHashCode =

    new AtomicInteger();

 

//1640531527

    private static final int HASH_INCREMENT = 0x61c88647;

    private static int nextHashCode() {

        return nextHashCode.getAndAdd(HASH_INCREMENT);

    }

 

 可以看到另外两个变量时辅助作用的,threadLocalHashCode才是真正使用的,AtomicInteger使得程序可以使得其他应用以原子方式更新int的值,getAndAdd(HASH_INCREMENT)表示每次增加常理,1640531527。而threadLocalHashCode是在保存到ThreadLocalMap的时候,作为一个hash函数计算的时候使用的。

 

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)

                return (T)e.value;

        }

        return setInitialValue();

}

 

getMap方法,返回当前线程的ThreadLocalMap,它被声明为Thread的包级别的变量:

threadLocals

 

当然,一开始的时候他是null,那么他会被setInitialValue方法创建:

 

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;

 }

Value一开始被设置为nullThreadLocalMapcreateMap方法创建:

 

void createMap(Thread t, T firstValue) {

        t.threadLocals = new ThreadLocalMap(this, firstValue);

}

 

ThreadLocalMap

 

/**

   * Construct a new map initially

containing (firstKey, firstValue).

   * ThreadLocalMaps are constructed lazily, so we only create

   * one when we have at least one entry to put in it.

 */

 ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {

//INITIAL_CAPACITY=16

       table = new Entry[INITIAL_CAPACITY];

      

//哈希函数的计算

int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);

       table[i] = new Entry(firstKey, firstValue);

       size = 1;

       setThreshold(INITIAL_CAPACITY);

 }

 

顺便看下Entry的构造函数:

static class Entry extends WeakReference<ThreadLocal> {

            /** The value associated with this ThreadLocal. */

            Object value;

 

            Entry(ThreadLocal k, Object v) {

                super(k);

                value = v;

            }

}

可以看到,ThreadLocal被作为一个弱引用,这样,一旦没有其他地方引用它,那么它就会在GC的时候被回收,ThreadLocal被设置为null时,ThreadLocalMap就会移除keyThreadLocalEntry(因为Entry本身就是一个弱引用对象)。Hashtable/HashMap的实现方式则无法实现这一点。而且以前Hashtable的实现需要同步,所带来的性能损耗是很大的,而现在的方式则不需要同步。性能提升很大。

 

 

ThreadLocalset代码很少,可以看下面,不过其实真正的地方,是在ThreadLocalMapset方法:

 /**

         * Set the value associated with key.

         *

         * @param key the thread local object

         * @param value the value to be 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.

 

            Entry[] tab = table;

            int len = tab.length;

            int i = key.threadLocalHashCode & (len-1);

 

            for (Entry e = tab[i]; e != null;

        e = tab[i = nextIndex(i, len)]) {

//引用的get方法,返回它所对应的那个对象

                ThreadLocal k = e.get();

 

//1.如果已经set过值,那么替换

                if (k == key) {

                    e.value = value;

                    return;

                }

 

//2.如果已经被回收,那么重新设置值

                if (k == null) {

                    replaceStaleEntry(key, value, i);

                    return;

                }

            }

 

            tab[i] = new Entry(key, value);

            int sz = ++size;

//3..计算是否需要重新扩充容量

            if (!cleanSomeSlots(i, sz) && sz >= threshold)

                rehash();

}

 

 

Set

接下来在看看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);

    }

 

可以看到这里有两步:

1.通过Thread获得他的ThreadLocalMap这里可以看出每个Thread实例对应自己的一个ThreadLocalMap

2.再将这个ThreadLocal作为key去保存set的这个value

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多