一幅图让你彻底明白ThreadLocal类型变量

看了些ThreadLocal类型变量的介绍,感觉都没有串起来说清楚。花了两小时搞清楚之后,为了其它伙伴们更容易理解ThreadLocal,咱们还是来个图文说明的方式,一图抵千言哪。如果能帮到你,还希望顶一下俺的原创。

-------
对不起啦,对java的静态内部类理解不足,下面的图对ThreadLocalMap的引用的画面可能问题,请大家指正!我会总结大家的意见修改后形成最终结论!非常感谢!
-------

针对ThreadLocal的源代码,我画了这样一张图,并把其set和get方法的伪代码写出来,相信你结合源码一下就能明白了:
先简单看看ThreadLocal源码:(在后面会为大家上图说明,并给出一个ThreadLocal简单用法)
public class ThreadLocal<T> {
    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();
    }
    ......

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    ......

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    ......
}

public class Thread implements Runnable {
    ......
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ......
}



总结的图如下:



  从图中可以看到,每个thread中都存在一个map,对此map作几下以点总结:
  1、由于map是属于当前线程的,自然在多线程环境中,其每一分ThreadLocal类型的变量的拷贝都是独立的,也就是线程安全的。这也是采用ThreadLocal变量与采用线程同步方式解决线程安全的最大区别——采用线程同步方式,类的成员变量作为多线程之间的共享资源;而采用ThreadLocal变量,根本就没有想到到共享,也不可能共享。

  2、map的类型是ThreadLocal的内部类ThreadLocalMap,为什么是内部类呢?这是因为该map要使用其外部类ThreadLocal的this指针作为key,这样可以保证同一个ThreadLocal类型的变量的拷贝只有唯一一份。另外注意个Map中的key是Threadlocal实例的弱引用,为什么使用弱引用呢?这个在后面为大家略作分析。

  3、ThreadLocalMap是ThreadLocal的静态内部类,为什么是静态呢?
threadLocals是定义在Thread类中的,如果不是静态内部类,那Thread类中定义这个变量还需要得到ThreadLocal类的实例,这是很不方便的。可能还有其它原因(没深入研究)。
另外,由于ThreadLocalMap是通过ThreadLocal变量的this指针作为key的,因此如果ThreadLocal变量不是同一个对象,那么拿到的ThreadLocal变量就是另一份拷贝。(后面的示例代码中会做简要说明)。

  4、因为ThreadLocalMap是独立于这个ThreadLocal变量的,所以在一个类中可以声明、定义多个ThreadLocal变量,这些ThreadLocal变量都会被确保是独立于线程的拷贝。

下面看一个例子:
public class TestThreadLocal {
    ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
    ThreadLocal<String> stringLocal = new ThreadLocal<String>();
 
     
    public void set() {
    	//key就是ThreadLocal变量自身(this)
    	System.out.println("set longLocal: " + "key=" + longLocal + ",value=" + Thread.currentThread().getId());
        longLocal.set(Thread.currentThread().getId());
        System.out.println("set stringLocal: "+ "key=" + stringLocal + ",value=" +  Thread.currentThread().getName());
        stringLocal.set(Thread.currentThread().getName());
    }
     
    public long getLong() {
    	System.out.print("getLongLocal: ");
        return longLocal.get();
    }
     
    public String getString() {
    	System.out.print("getStringLocal: ");
        return stringLocal.get();
    }
     
    public static void main(String[] args) throws InterruptedException {
        final TestThreadLocal test = new TestThreadLocal();
        printThreadName(); 
        test.set();
        System.out.println(test.getLong());
        System.out.println(test.getString());
     
         
        Thread thread1 = new Thread(){
            public void run() {
            	printThreadName(); 
                test.set();
                System.out.println(test.getLong());
                System.out.println(test.getString());
            };
        };
        thread1.start();
        thread1.join();
        
        Thread thread2 = new Thread(){
        	TestThreadLocal test2 = new TestThreadLocal();
            public void run() {
            	printThreadName(); 
                test2.set();
                System.out.println(test2.getLong());
                System.out.println(test2.getString());
            };
        };
        thread2.start();
        thread2.join();
    
        printThreadName(); 
        System.out.println(test.getLong());
        System.out.println(test.getString());
    }
    
    private static void printThreadName(){
    	System.out.println("-------------------"); 
        System.out.println("[IN Thread " + Thread.currentThread().getName() + "]"); 
    }
}


运行结果如下:
可以明显看出:main线程和Thread-0采用的key都是同一对象,因此在两个线程中使用同一key但保存了独立于线程的不同拷贝;Thread-1由于是重新new出来的TestThreadLocal,因此采用的key则是不同的key,所以取出来的是另一份拷贝。(因此如果要取到的是同一对象,必须要求其key是同一对象)
-------------------
[IN Thread main]
set longLocal: key=java.lang.ThreadLocal@11671b2,value=1
set stringLocal: key=java.lang.ThreadLocal@12452e8,value=main
getLongLocal: 1
getStringLocal: main
-------------------
[IN Thread Thread-0]
set longLocal: key=java.lang.ThreadLocal@11671b2,value=8
set stringLocal: key=java.lang.ThreadLocal@12452e8,value=Thread-0
getLongLocal: 8
getStringLocal: Thread-0
-------------------
[IN Thread Thread-1]
set longLocal: key=java.lang.ThreadLocal@1e4f7c2,value=9
set stringLocal: key=java.lang.ThreadLocal@145f0e3,value=Thread-1
getLongLocal: 9
getStringLocal: Thread-1
-------------------
[IN Thread main]
getLongLocal: 1
getStringLocal: main

猜你喜欢

转载自hwei199.iteye.com/blog/2258698