Java并发机制(4)--ThreadLocal线程本地变量(转)

个人理解:

  说明:看了博客园中大神写的ThreadLocal的详解,感觉还是有些迷糊,下面用自己的理解简单描述下ThreadLocal的机制(难免有误);

  1、首先ThreadLocal用于存储对应线程的本地变量,放在哪里呢?每一个线程维护一个threadlocals(这个threadlocals我认为是由ThreadLcoal创建的,是当前线程上的属性,多个ThreadLocal只创建一个:通过ThreadLocal中的createMap方法: t.threadLocals = new ThreadLocalMap(this, firstValue); ),它事实上就是一个ThreadLocalMap,是ThreadLocal的内部类,可以理解为一张Map表,其中包含了Entry键值对,故线程的本地变量就放在这个Entry中:Entry:<ThreadLocal,value>形式,键值对里的ThreadLocal我认为是你用的时候new出来的ThreadLocal,而不是每个线程所维护的那一个ThreadLoca。(不是很确定,但是想到一个线程要放多个变量的话,每个变量new一个ThreadLocal,这些ThreadLocal做为key放在线程所维护的那个ThreadLocalMap中?)。

  2、由1中知道,一个ThreadLocal只能存放一个线程的本地变量,所以要存放第二个线程的本地变量,就应该在new ThreadLocal进行存储,所以  private static final ThreadLocal <T> t1=new Threadlocal<>()  这句话可以写在工具类中,且作为静态成员变量使用。

  这里我一开始理解错误:把ThreadLocalMap理解成类似于HashMaple了,然后ThreadLocal保存了所有线程的本地变量,这样一个ThreadLocal就可以了?那么多线程访问一个ThreadLocal(共享)依然需要加锁,失去了原来的意义。

  3、ThreadLocal的方法理解(源码分析):

    3.1、threadlocal.get()方法:

        step1:根据当前线程t获取对应的TheradLocalMap;

        step2:如果ThreadLocalMap!=null;获取内部的Entry,返回val,结束;

             如果ThreadLocalMap==null,调用setInitialValue()方法设置初始值;

        step3:setInitialValue()方法判断是否有ThreadLocalMap,(没有则创建),

            并调用initialValue()方法获得初始value,这个value=null;

        所以,new 出来的Threadlocal要先set(value),然后get(),否则get到null;

          或者也可以重写initialValue方法(默认返回一个初始值);

    3.2、threadlocal.set()方法:赋值,不多描述。

  4、弱引用问题:ThreadLocalMap使用ThreadLocal的弱引用作为key,,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,无法访问到这些value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value,永远无法回收,造成内存泄露。

   JDK建议将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,并主动调用remove函数释放。

  总结如下:

    首先,要区分threadlocals、ThreadLocalMap、ThreadLocal这三者。

    其次,ThreadLcoal的主要机制如下。

    (1)每个线程Thread内部有一个threadlocals属性,它是ThreadLocal.ThreadLocalMap类型的,内部的Entry<ThreadLocal,value>用来保存变量的副本,只供线程自己使用。

    (2)最开始,threadlocals是空的,通过Threadlocal的get或者set方法可以初始化threadlocals(调用createMap方法,源代码在1种),然后你每存一个变量副本,需要new ThreadLocal()出来,存几个new几个,threadlocals以该ThreadLocal为key,变量副本为value进行保存。

    (2)我们可以通过ThreadLocal.get()获取对应的ThreadLocal的value。通过ThreadLcoal.set(value)来设置更改threadlocals中的值。

参考资料:

  1、博客园-海子-http://www.cnblogs.com/dolphin0520/p/3920407.html

  2、http://www.iteye.com/topic/103804

  3、http://www.cnblogs.com/xzwblog/p/7227509.html

  以下给出两段实例代码,也来自上述参考资料:

1、hibernate中典型的ThreadLocal的应用,获取session;

...
private static final ThreadLocal threadSession = new ThreadLocal();  //类成员
  
public static Session getSession() throws InfrastructureException {  
    Session s = (Session) threadSession.get();  
    try {  
        if (s == null) {  
            s = getSessionFactory().openSession();  
            threadSession.set(s);  
        }  
    } catch (HibernateException ex) {  
        throw new InfrastructureException(ex);  
    }  
    return s;  
}  

 2、

public class Test {
    ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
    ThreadLocal<String> stringLocal = new ThreadLocal<String>();
 
     
    public void set() {
        longLocal.set(Thread.currentThread().getId());

                     //这里先获取每一个线程thread的ThreadLocalMap,然后map.set(this, value),this就是当前的ThreadLocal,value就是getId的值,也就为每个线程创建完副本,并存放入线程的ThreadLocalMap中,从而实现了线程本地变量的副本创建。

        stringLocal.set(Thread.currentThread().getName());
    }
     
    public long getLong() {
        return longLocal.get();
    }
     
    public String getString() {
        return stringLocal.get();
    }
     
    public static void main(String[] args) throws InterruptedException {
        final Test test = new Test();
         
         
        test.set();
        System.out.println(test.getLong());
        System.out.println(test.getString());
     
         
        Thread thread1 = new Thread(){
            public void run() {
                test.set();
                System.out.println(test.getLong());
                System.out.println(test.getString());
            };
        };
        thread1.start();
        thread1.join();
         
        System.out.println(test.getLong());
        System.out.println(test.getString());
    }
}

  

------------------------------------------------------------------------------------------终-----------------------------------------------------------------------------------------------------------------------------------------------

   

详情移步:博客园-海子-http://www.cnblogs.com/dolphin0520/p/3920407.html

 
 
 
原文地址:https://www.cnblogs.com/whtblog/p/8882340.html