Java中的ThreadLocal的实现原理分析

1.概述

ThreadLocal为我们解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。
ThreadLocal很容易让人望文生义,想当然地认为是一个“本地线程”。其实,ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名ThreadLocalVariable更容易让人理解一些。
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

2.ThreadLocal的接口方法

ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:
void set(Object value)
设置当前线程的线程局部变量的值。
public Object get()
该方法返回当前线程所对应的线程局部变量。
public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。

3.实现原理

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。我们自己就可以提供一个简单的实现版本:
[java] view plain copy
  1. publicclassThreadLocal{
  2.  privateMapvalues=Collections.synchronizedMap(newHashMap()); 
  3. publicObjectget(){
  4.   ThreadcurThread=Thread.currentThread();
  5.   Objecto=values.get(curThread);
  6.   if(o==null||!values.containsKey(curThread)){
  7.   o=initialValue();
  8.   values.put(curThread,o);
  9. }
  10.   returno; 
  11. }
  12. publicvoidset(ObjectnewValue){
  13.   values.put(Thread.currentThread(),newValue);
  14. }
  15.  publicObjectinitialValue(){
  16.   returnnull;
  17. }
  18. }

4.Thread同步机制的比较

ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK 5.0通过泛型很好的解决了这个问题,在一定程度地简化ThreadLocal的使用,代码清单 9 2就使用了JDK 5.0新的ThreadLocal<T>版本。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。

5.小结

当然ThreadLocal并不能替代同步机制,两者面向的问题领域不同。同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有效方式;而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源(变量),这样当然不需要对多个线程进行同步了。所以,如果你需要进行多个线程之间进行通信,则使用同步机制;如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal,这将极大地简化你的程序,使程序更加易读、简洁。

6.使用案例:

import java.util.Random;
public class ThreadLocalTest implements Runnable {
Student student = new Student();
ThreadLocal<Student> threadLocal = new ThreadLocal<Student>() {
@Override
protected Student initialValue() {//重写initialValue,实现在获取变量的拷贝而不是源变量
System.out.println("initialValue of student");
return new Student(888);
}
};

public static void main(String[] args) {
ThreadLocalTest ts = new ThreadLocalTest();
Thread t1 = new Thread(ts,"t1");
Thread t2 = new Thread(ts,"t2");
t1.start();
t2.start();
}

public void run() {
String currentThreadName = Thread.currentThread().getName();
System.out.println("CurrentThread is:"+currentThreadName);
Random random = new Random();
int age = random.nextInt(100);
System.out.println("thread "+currentThreadName+" set age to "+age);//当前线程Student.age
Student tstudent = threadLocal.get();//获取当前线程的变量
if(tstudent == this.student) {
System.out.println("there is no copy...................");
}
tstudent.setAge(age);//改变年龄
System.out.println("thread " + currentThreadName + " set age to " + tstudent.getAge());//当前线程Student.age
System.out.println("main thread student age is " + student.getAge());//主线程中student.age
try {
Thread.sleep(500);
} catch(Exception e) {

}
}
}
class Student {
private int age = 0;
public Student(){}
public Student(int a) {
this.age = a;
}
public void setAge (int a) {
this.age = a;
return;
}

public int getAge () {
return this.age;
}
}

运行结果:

CurrentThread is:t1
CurrentThread is:t2
thread t1 set age to 33
thread t2 set age to 40
initialValue of student
initialValue of student
thread t2 set age to 40
thread t1 set age to 33
main thread student age is 0
main thread student age is 0


你可能感兴趣的:(threadLocal)