【Java.Concurrency】ThreadLocal

来源:互联网 发布:读读日报和知乎日报 编辑:程序博客网 时间:2024/06/11 01:17

ThreadLocal概述

package java.langpublic class ThreadLocal<T> extends Object

Methods Modifier and TypeMethod and DescriptionTget()

Returns the value in the current thread's copy of this thread-local variable.
protected TinitialValue()
Returns the current thread's "initial value" for this thread-local variable.
voidremove()
Removes the current thread's value for this thread-local variable.
voidset(T value)
Sets the current thread's copy of this thread-local variable to the specified value.

From Java Doc:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).


提供的示例

该类为每个线程生成一个唯一的ID,当第一次调用方法ThreadId.get()时,为该线程分配一个ID值,在之后的调用中保持该值不变:

 public class ThreadId {     // Atomic integer containing the next thread ID to be assigned     private static final AtomicInteger nextId = new AtomicInteger(0);     // Thread local variable containing each thread's ID     private static final ThreadLocal<Integer> threadId =         new ThreadLocal<Integer>() {             @Override protected Integer initialValue() {                 return nextId.getAndIncrement();         }     };     // Returns the current thread's unique ID, assigning it if necessary     public static int get() {         return threadId.get();     } }



ThreadLocal的实现/原理

常见误解

以为在ThreadLocal里使用一个Map,这个Map的键为Thread,值为绑定的变量。其实如果这样做是有问题的: 

  • 就是当线程回收时,该线程绑定的变量不能被自动的回收,因为变量存储在ThreadLocal里,必须显式的去回收。如果此变量存储在线程里,那么线程回收时,这个变量没有被其他引用指向的话,它便随着线程一起回收。 
  • 另外不这样做还有一个好处:如果Map在ThreadLocal里,那你必须得考虑线程同步访问这个Map,但是这确实没有必要,因为线程访问自己的变量,和其他线程没有直接的关系,所以把Map放在线程里,就不需要做同步的处理,这样即加快了访问的速度。 

每个线程Thread都包含一个ThreadLocal.ThreadLocalMap类型的变量threadLocals(延迟创建的),这个Map目的就是为每个线程存储关联到使用到的不同ThreadLocal的变量,这个很好理解,因为,一个线程可能使用到多个不同的ThreadLocal对象,每一个ThreadLocal对象的值都被认为是不同的。于是,每次调用ThreadLocal的get()方法,其实就是获取当前线程(Thread.currentThread()),然后从threadLocals映射里,根据ThreadLocal对象,找出其关联的拷贝,这个值便是当前线程的,隔离于其他线程的值。 

ThreadLocal.ThreadLocalMap映射使用的键是被WeakReference包装的ThreadLocal对象,如果ThreadLocal对象没有其他强引用和软引用指向时,该线程也不会继续持有ThreadLocal对象,因为根据JVM规范,它会被垃圾回收器下次回收时销毁,这一定程度避免了内存泄露,但不表示不会出现内存泄露,关于ThreadLocal引起的内存泄露,特别是导致ClassLoader不能被回收,网上有很多文章都在讨论。在Java 1.5开始,加入了remove()方法,这样我们可以显式的调用此方法,释放内存,所以使用ThreadLocal要特别注意内存泄露的问题。 


ThreadLocal的使用场景

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

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


总之,ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式: 

  • 每个线程对象都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。 
  • 将一个共用的ThreadLocal静态实例(通常的使用方法)作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。 
当然如果要把本来线程共享的对象通过ThreadLocal.set()放到线程中也可以,可以实现避免参数传递的访问方式,但是要注意get()到的是那同一个共享对象,并发访问问题要靠其他手段来解决。但一般来说线程共享的对象通过设置为某类的静态变量就可以实现方便的访问了,似乎没必要放到线程中。 


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

ThreadLocal的源码分析



0 0
原创粉丝点击