引言: 在Java的多线程编程中,竞争资源的同步是一个需要格外关注的问题。处理使用volatile和同步锁机制实现资源访问的一致性之外,还可以使用ThreadLocal来保存线程的私有变量,从而避免了竞争资源的产生。
1. ThreadLocal是什么?
ThreadLocal是服务于Thread的一种本地私有数据机制,threadlocalvariable(线程局部变量), 即为每一个使用该变量的线程都提供一个变量值的副本,与使用的线程绑定,每一个线程都可以独立地改变自己的副本,不会产生冲突。
其在Thread处于活跃状态,并且threadLocal对象可用的情况下,就可以在其中存放数据。在线程被销毁之后,则ThreadLocal中使用的资源和内存同时也被回收。
本质上,JVM通过ThreadLocal为多线程情况下,进行资源的访问提供了一种隔离机制,简化了编程的复杂度。
2. ThreadLocal的用法
2.1 ThreadLocal的主要方法
- ThreadLocal() : 创建实例
- T get() :返回当前线程中的值,如果未设置,则返回初始值
- void remove() : 移除当前线程的值,并释放资源
- void set(T value) :设置当前线程线程副本中的
- protected void initialValue(): 用以在子类中被重写其初始值
由此可以看出此线程局部变量只可以存放一个对象。
2.2 ThreadLocal使用示例
- package org.test;
- public class ThreadLocalTest extends Thread {
- //必须的static final
- private final ThreadLocal<Student> mythreadLocal = new ThreadLocal<Student>();
- private int age;
- public ThreadLocalTest(String name) {
- super(name);
- }
- public ThreadLocalTest(String name, int age) {
- super(name);
- this.age = age;
- }
- public static void main(String[] args) {
- ThreadLocalTest student1 = new ThreadLocalTest("thread-first",23);
- ThreadLocalTest student2 = new ThreadLocalTest("thread-second",30);
- student1.start();
- student2.start();
- }
- public void run() {
- testInfo();
- }
- public void testInfo() {
- String currentThreadName = Thread.currentThread().getName();
- System.out.println(currentThreadName + " is running!");
- Student stud1 = this.getLocalVariable();
- System.out.println("Student " + stud1.name + " is in age " + stud1.age + " in " + currentThreadName);
- }
- public Student getLocalVariable() {
- if (mythreadLocal.get() == null) {
- Student student = new Student(Thread.currentThread().getName() + "-student", this.age);
- mythreadLocal.set(student);
- }
- return mythreadLocal.get();
- }
- public class Student {
- private String name;
- private int age;
- public Student(){}
- public Student(String name, int age) {
- this.name = name;
- this.age = age;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- }
- }
- Thread-1 is running!
- Thread-0 is running!
- Student LiSi is in age 30 in Thread-1
- Student ZhangSan is in age 24 in Thread-0
注意: 这里的ThreadLocal按照JavaDoc文档的说明,应该声明为private static,因为它是在多个线程之间共享的一个数据结构,类似Map,以thread对象实例做为Key的。
3. ThreadLocal和同步锁机制的对比分析
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
同步机制利用所实现资源的同步访问,确保某一个时刻只有一个线程在访问资源;而ThreadLoca则规避了同步,让每一个线程有自己的一份副本。
他们之间不能彼此替代,只是从不同的角度去解决线程访问资源的问题。threadLocal无法替代锁实现的资源共享,而锁也做不到可以提供给独立的线程实例资源。
4. 基于JVM的内存模型来分析ThreadLocal
ThreadLocal本质上是一个类似Map的结构,以各个线程对象本身为Key,将其值存放进去。这个结构在所有的基于同一个线程类创建出来的线程中被共享所有,就是只有一个,单例的对象。 虽然,使用get()方法来读取其值,但是默认的是使用当前的thread对象做为Key来检索的。
具体的实现机制可以参照源码分析。
5. ThreadLocal的源码分析
首先,来看看get()方法的源代码
- 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(); //如果为空,则设置初始值
- }
- public void set(T value) {
- Thread t = Thread.currentThread(); //线程对象实例
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value); //设值
- else
- createMap(t, value); // 创建,设置
- }
基于以上的代码,我们可以清楚的发现,其实就是基于对象实例的Map实现。
6. 总结
ThreadLocal存放的内容,不支持基本数据类型,只支持对象类型。用以存放线程私有的数据,以规避繁琐的同步机制下的资源共享。一般而言,少量数据是可以通过这种简便的方式而实现线程访问的。这些数据不涉及到线程之间的通信和共享问题。
参考文档
1. http://lavasoft.blog.51cto.com/62575/51926/
2. http://docs.oracle.com/javase/6/docs/api/java/lang/ThreadLocal.html