正确理解ThreadLocal

7 篇文章 0 订阅
4 篇文章 0 订阅
首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。   各个线程中访问的是不同的对象。   

另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。   通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。   

如果ThreadLocal.set()进去的东西本来就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。   

下面来看一个hibernate中典型的ThreadLocal的应用: 

01 private static final ThreadLocal threadSession = new ThreadLocal();
02  
03     public static Session getSession() throws InfrastructureException {
04         Session s = (Session) threadSession.get();
05         try {
06             if (s == null) {
07                 s = getSessionFactory().openSession();
08                 threadSession.set(s);
09             }
10         catch (HibernateException ex) {
11             throw new InfrastructureException(ex);
12         }
13         return s;
14     }
可以看到在getSession()方法中,首先判断当前线程中有没有放进去session,如果还没有,那么通过sessionFactory().openSession()来创建一个session,再将session set到线程中,实际是放到当前线程的ThreadLocalMap这个map中,这时,对于这个session的唯一引用就是当前线程中的那个ThreadLocalMap(下面会讲到),而threadSession作为这个值的key,要取得这个session可以通过threadSession.get()来得到,里面执行的操作实际是先取得当前线程中的ThreadLocalMap,然后将threadSession作为key将对应的值取出。这个session相当于线程的私有变量,而不是public的。   
显然,其他线程中是取不到这个session的,他们也只能取到自己的ThreadLocalMap中的东西。要是session是多个线程共享使用的,那还不乱套了。   
试想如果不用ThreadLocal怎么来实现呢?可能就要在action中创建session,然后把session一个个传到service和dao中,这可够麻烦的。或者可以自己定义一个静态的map,将当前thread作为key,创建的session作为值,put到map中,应该也行,这也是一般人的想法,但事实上,ThreadLocal的实现刚好相反,它是在每个线程中有一个map,而将ThreadLocal实例作为key,这样每个map中的项数很少,而且当线程销毁时相应的东西也一起销毁了,不知道除了这些还有什么其他的好处。   

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

当然如果要把本来线程共享的对象通过ThreadLocal.set()放到线程中也可以,可以实现避免参数传递的访问方式,但是要注意get()到的是那同一个共享对象,并发访问问题要靠其他手段来解决。但一般来说线程共享的对象通过设置为某类的静态变量就可以实现方便的访问了,似乎没必要放到线程中。   

ThreadLocal的应用场合,我觉得最适合的是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。      

下面来看看ThreadLocal的实现原理(jdk1.5源码) 

001 public class ThreadLocal<T> {
002     /**
003      * ThreadLocals rely on per-thread hash maps attached to each thread
004      * (Thread.threadLocals and inheritableThreadLocals).  The ThreadLocal
005      * objects act as keys, searched via threadLocalHashCode.  This is a
006      * custom hash code (useful only within ThreadLocalMaps) that eliminates
007      * collisions in the common case where consecutively constructed
008      * ThreadLocals are used by the same threads, while remaining well-behaved
009      * in less common cases.
010      */
011     private final int threadLocalHashCode = nextHashCode();
012  
013     /**
014      * The next hash code to be given out. Accessed only by like-named method.
015      */
016     private static int nextHashCode = 0;
017  
018     /**
019      * The difference between successively generated hash codes - turns
020      * implicit sequential thread-local IDs into near-optimally spread
021      * multiplicative hash values for power-of-two-sized tables.
022      */
023     private static final int HASH_INCREMENT = 0x61c88647;
024  
025     /**
026      * Compute the next hash code. The static synchronization used here
027      * should not be a performance bottleneck. When ThreadLocals are
028      * generated in different threads at a fast enough rate to regularly
029      * contend on this lock, memory contention is by far a more serious
030      * problem than lock contention.
031      */
032     private static synchronized int nextHashCode() {
033         int h = nextHashCode;
034         nextHashCode = h + HASH_INCREMENT;
035         return h;
036     }
037  
038     /**
039      * Creates a thread local variable.
040      */
041     public ThreadLocal() {
042     }
043  
044     /**
045      * Returns the value in the current thread's copy of this thread-local
046      * variable.  Creates and initializes the copy if this is the first time
047      * the thread has called this method.
048      *
049      * @return the current thread's value of this thread-local
050      */
051     public T get() {
052         Thread t = Thread.currentThread();
053         ThreadLocalMap map = getMap(t);
054         if (map != null)
055             return (T)map.get(this);
056  
057         // Maps are constructed lazily.  if the map for this thread
058         // doesn't exist, create it, with this ThreadLocal and its
059         // initial value as its only entry.
060         T value = initialValue();
061         createMap(t, value);
062         return value;
063     }
064  
065     /**
066      * Sets the current thread's copy of this thread-local variable
067      * to the specified value.  Many applications will have no need for
068      * this functionality, relying solely on the {@link #initialValue}
069      * method to set the values of thread-locals.
070      *
071      * @param value the value to be stored in the current threads' copy of
072      *        this thread-local.
073      */
074     public void set(T value) {
075         Thread t = Thread.currentThread();
076         ThreadLocalMap map = getMap(t);
077         if (map != null)
078             map.set(this, value);
079         else
080             createMap(t, value);
081     }
082  
083     /**
084      * Get the map associated with a ThreadLocal. Overridden in
085      * InheritableThreadLocal.
086      *
087      * @param  t the current thread
088      * @return the map
089      */
090     ThreadLocalMap getMap(Thread t) {
091         return t.threadLocals;
092     }
093  
094     /**
095      * Create the map associated with a ThreadLocal. Overridden in
096      * InheritableThreadLocal.
097      *
098      * @param t the current thread
099      * @param firstValue value for the initial entry of the map
100      * @param map the map to store.
101      */
102     void createMap(Thread t, T firstValue) {
103         t.threadLocals = new ThreadLocalMap(this, firstValue);
104     }
105  
106     .......
107  
108     /**
109      * ThreadLocalMap is a customized hash map suitable only for
110      * maintaining thread local values. No operations are exported
111      * outside of the ThreadLocal class. The class is package private to
112      * allow declaration of fields in class Thread.  To help deal with
113      * very large and long-lived usages, the hash table entries use
114      * WeakReferences for keys. However, since reference queues are not
115      * used, stale entries are guaranteed to be removed only when
116      * the table starts running out of space.
117      */
118     static class ThreadLocalMap {
119  
120     ........
121  
122     }
123  
124 }
可以看到ThreadLocal类中的变量只有这3个int型: 

1 private final int threadLocalHashCode = nextHashCode();
2     private static int nextHashCode = 0;
3     private static final int HASH_INCREMENT = 0x61c88647;
而作为ThreadLocal实例的变量只有 threadLocalHashCode 这一个,nextHashCode 和HASH_INCREMENT 是ThreadLocal类的静态变量,实际上HASH_INCREMENT是一个常量,表示了连续分配的两个ThreadLocal实例的threadLocalHashCode值的增量,而nextHashCode 的表示了即将分配的下一个ThreadLocal实例的threadLocalHashCode 的值。   

可以来看一下创建一个ThreadLocal实例即new ThreadLocal()时做了哪些操作,从上面看到构造函数ThreadLocal()里什么操作都没有,唯一的操作是这句: 


1 private final int threadLocalHashCode = nextHashCode();

那么nextHashCode()做了什么呢: 


1 private static synchronized int nextHashCode() {
2         int h = nextHashCode;
3         nextHashCode = h + HASH_INCREMENT;
4         return h;
5     }
就是将ThreadLocal类的下一个hashCode值即nextHashCode的值赋给实例的threadLocalHashCode,然后nextHashCode的值增加HASH_INCREMENT这个值。   

因此ThreadLocal实例的变量只有这个threadLocalHashCode,而且是final的,用来区分不同的ThreadLocal实例,ThreadLocal类主要是作为工具类来使用,那么ThreadLocal.set()进去的对象是放在哪儿的呢?   

看一下上面的set()方法,两句合并一下成为 


1 ThreadLocalMap map = Thread.currentThread().threadLocals;
这个ThreadLocalMap 类是ThreadLocal中定义的内部类,但是它的实例却用在Thread类中: 


1 public class Thread implements Runnable {
2     ......
3  
4     /* ThreadLocal values pertaining to this thread. This map is maintained
5      * by the ThreadLocal class. */
6     ThreadLocal.ThreadLocalMap threadLocals = null
7     ......
8 }

再看这句: 

1 if (map != null)
2             map.set(this, value);



也就是将该ThreadLocal实例作为key,要保持的对象作为值,设置到当前线程的ThreadLocalMap 中,get()方法同样大家看了代码也就明白了,ThreadLocalMap 类的代码太多了,我就不帖了,自己去看源码吧。 


贴个例子吧:


01 public class Student {
02     private int age = 0;   //年龄
03   
04     public int getAge() {
05         return this.age;
06     }
07   
08     public void setAge(int age) {
09         this.age = age;
10     }
11 }
12   
13  
14 public class ThreadLocalDemo implements Runnable {
15     //创建线程局部变量studentLocal,在后面你会发现用来保存Student对象
16     private final static ThreadLocal studentLocal = new ThreadLocal();
17   
18     public static void main(String[] agrs) {
19         ThreadLocalDemo td = new ThreadLocalDemo();
20         Thread t1 = new Thread(td, "a");
21         Thread t2 = new Thread(td, "b");
22         t1.start();
23         t2.start();
24     }
25   
26     public void run() {
27         accessStudent();
28     }
29   
30     /**
31      * 示例业务方法,用来测试
32      */
33     public void accessStudent() {
34         //获取当前线程的名字
35         String currentThreadName = Thread.currentThread().getName();
36         System.out.println(currentThreadName + " is running!");
37         //产生一个随机数并打印
38         Random random = new Random();
39         int age = random.nextInt(100);
40         System.out.println("thread " + currentThreadName + " set age to:" + age);
41         //获取一个Student对象,并将随机数年龄插入到对象属性中
42         Student student = getStudent();
43         student.setAge(age);
44         System.out.println("thread " + currentThreadName + " first read age is:" + student.getAge());
45         try {
46             Thread.sleep(500);
47         }
48         catch (InterruptedException ex) {
49             ex.printStackTrace();
50         }
51         System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge());
52     }
53   
54     protected Student getStudent() {
55         //获取本地线程变量并强制转换为Student类型
56         Student student = (Student) studentLocal.get();
57         //线程首次执行此方法的时候,studentLocal.get()肯定为null
58         if (student == null) {
59             //创建一个Student对象,并保存到本地线程变量studentLocal中
60             student = new Student();
61             studentLocal.set(student);
62         }
63         return student;
64     }
65 }



结果:
1 a is running!
2 thread a set age to:76
3 b is running!
4 thread b set age to:27
5 thread a first read age is:76
6 thread b first read age is:27
7 thread a second read age is:76
8 thread b second read age is:27

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值