彻底理解ThreadLocal

ThreadLocal是什么

  早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。

  当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

     ThreadLocal很容易让人望文生义,想当然地认为是一个“本地线程”。其实,ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些。

  从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。

  所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,因此造成线程局部变量没有在Java开发者中得到很好的普及。

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()。

  ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。我们自己就可以提供一个简单的实现版本:

package com.test;

public class TestNum {
	// ①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
	private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {
		public Integer initialValue() {
			return 0;
		}
	};

	// ②获取下一个序列值
	public int getNextNum() {
		seqNum.set(seqNum.get() + 1);
		return seqNum.get();
	}

	public static void main(String[] args) {
		TestNum sn = new TestNum();
		// ③ 3个线程共享sn,各自产生序列号
		TestClient t1 = new TestClient(sn);
		TestClient t2 = new TestClient(sn);
		TestClient t3 = new TestClient(sn);
		t1.start();
		t2.start();
		t3.start();
	}

	private static class TestClient extends Thread {
		private TestNum sn;

		public TestClient(TestNum sn) {
			this.sn = sn;
		}

		public void run() {
			for (int i = 0; i < 3; i++) {
				// ④每个线程打出3个序列值
				System.out.println("thread[" + Thread.currentThread().getName() + "] --> sn["
						 + sn.getNextNum() + "]");
			}
		}
	}
}<span style="font-family:Arial;"><span style="font-size: 14px; line-height: 26px;">
</span></span>


通常我们通过匿名内部类的方式定义ThreadLocal的子类,提供初始的变量值,如例子中①处所示。TestClient线程产生一组序列号,在③处,我们生成3个TestClient,它们共享同一个TestNum实例。运行以上代码,在控制台上输出以下的结果:

thread[Thread-0] --> sn[1]
thread[Thread-1] --> sn[1]
thread[Thread-2] --> sn[1]
thread[Thread-1] --> sn[2]
thread[Thread-0] --> sn[2]
thread[Thread-1] --> sn[3]
thread[Thread-2] --> sn[2]
thread[Thread-0] --> sn[3]
thread[Thread-2] --> sn[3]

考察输出的结果信息,我们发现每个线程所产生的序号虽然都共享同一个TestNum实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序列号,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本。


Thread同步机制的比较

  ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。

  在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。

  而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

  由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK 5.0通过泛型很好的解决了这个问题,在一定程度地简化ThreadLocal的使用,代码清单 9 2就使用了JDK 5.0新的ThreadLocal<T>版本。

  概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

  Spring使用ThreadLocal解决线程安全问题我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。

  一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程,如图9‑2所示:



同一线程贯通三层这样你就可以根据需要,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有关联的对象引用到的都是同一个变量。

  下面的实例能够体现Spring对有状态Bean的改造思路:

代码清单3 TestDao:非线程安全

package com.test;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

public class TestDao {
	private Connection conn;// ①一个非线程安全的变量

	public void addTopic() throws SQLException {
		Statement stat = conn.createStatement();// ②引用非线程安全变量
		// …
	}
}


由于①处的conn是成员变量,因为addTopic()方法是非线程安全的,必须在使用时创建一个新TopicDao实例(非singleton)。下面使用ThreadLocal对conn这个非线程安全的“状态”进行改造:

代码清单4 TestDao:线程安全

package com.test;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

public class TestDaoNew {
	// ①使用ThreadLocal保存Connection变量
	private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();

	public static Connection getConnection() {
		// ②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection,
		// 并将其保存到线程本地变量中。
		if (connThreadLocal.get() == null) {
			Connection conn = getConnection();
			connThreadLocal.set(conn);
			return conn;
		} else {
			return connThreadLocal.get();// ③直接返回线程本地变量
		}
	}

	public void addTopic() throws SQLException {
		// ④从ThreadLocal中获取线程对应的Connection
		Statement stat = getConnection().createStatement();
	}
}

不同的线程在使用TopicDao时,先判断connThreadLocal.get()是否是null,如果是null,则说明当前线程还没有对应的Connection对象,这时创建一个Connection对象并添加到本地线程变量中;如果不为null,则说明当前的线程已经拥有了Connection对象,直接使用就可以了。这样,就保证了不同的线程使用线程相关的Connection,而不会使用其它线程的Connection。因此,这个TopicDao就可以做到singleton共享了。

  当然,这个例子本身很粗糙,将Connection的ThreadLocal直接放在DAO只能做到本DAO的多个方法共享Connection时不发生线程安全问题,但无法和其它DAO共用同一个Connection,要做到同一事务多DAO共享同一Connection,必须在一个共同的外部类使用ThreadLocal保存Connection。



ConnectionManager.java

package com.test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class ConnectionManager {

	private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
		@Override
		protected Connection initialValue() {
			Connection conn = null;
			try {
				conn = DriverManager.getConnection(
						"jdbc:mysql://localhost:3306/test", "username",
						"password");
			} catch (SQLException e) {
				e.printStackTrace();
			}
			return conn;
		}
	};

	public static Connection getConnection() {
		return connectionHolder.get();
	}

	public static void setConnection(Connection conn) {
		connectionHolder.set(conn);
	}
}


java.lang.ThreadLocal<T>的具体实现

那么到底ThreadLocal类是如何实现这种“为每个线程提供不同的变量拷贝”的呢?先来看一下ThreadLocal的set()方法的源码是如何实现的:

 /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }


在这个方法内部我们看到,首先通过getMap(Thread t)方法获取一个和当前线程相关的ThreadLocalMap,然后将变量的值设置到这个ThreadLocalMap对象中,当然如果获取到的ThreadLocalMap对象为空,就通过createMap方法创建。


线程隔离的秘密,就在于ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。


为了加深理解,我们接着看上面代码中出现的getMap和createMap方法的实现:

<span style="font-family:Arial;"><span style="font-size: 14px; line-height: 26px;">   </span></span> /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     * @param map the map to store.
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }


接下来再看一下ThreadLocal类中的get()方法:

<span style="font-family:Arial;"><span style="font-size: 14px; line-height: 26px;">   </span></span> /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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();
    }


再来看setInitialValue()方法:

<span style="font-family:Arial;"><span style="font-size: 14px; line-height: 26px;"> </span></span>/**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }


获取和当前线程绑定的值时,ThreadLocalMap对象是以this指向的ThreadLocal对象为键进行查找的,这当然和前面set()方法的代码是相呼应的。


  进一步地,我们可以创建不同的ThreadLocal实例来实现多个变量在不同线程间的访问隔离,为什么可以这么做?因为不同的ThreadLocal对象作为不同键,当然也可以在线程的ThreadLocalMap对象中设置不同的值了。通过ThreadLocal对象,在多线程中共享一个值和多个值的区别,就像你在一个HashMap对象中存储一个键值对和多个键值对一样,仅此而已。


小结

  ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

ConnectionManager.java

package com.test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class ConnectionManager {

	private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
		@Override
		protected Connection initialValue() {
			Connection conn = null;
			try {
				conn = DriverManager.getConnection(
						"jdbc:mysql://localhost:3306/test", "username",
						"password");
			} catch (SQLException e) {
				e.printStackTrace();
			}
			return conn;
		}
	};

	public static Connection getConnection() {
		return connectionHolder.get();
	}

	public static void setConnection(Connection conn) {
		connectionHolder.set(conn);
	}
}


后记

  看到网友评论的很激烈,甚至关于ThreadLocalMap不是ThreadLocal里面的,而是Thread里面的这种评论都出现了,于是有了这个后记,下面先把jdk源码贴上,源码最有说服力了。

/**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap {...}

源码就是以上,这源码自然是在ThreadLocal里面的,有截图为证。



 本文是自己在学习ThreadLocal的时候,一时兴起,深入看了源码,思考了此类的作用、使用范围,进而联想到对传统的synchronize共享变量线程安全的问题进行比较,而总结的博文,总结一句话就是一个是锁机制进行时间换空间,一个是存储拷贝进行空间换时间。


(全文完)




文章二:

ThreadLocal概念

ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

ThreadLocal与Connection

疑问1:有一个用户请求就会启动一个线程。而如果ThreadLocal用的是变量副本,那我们把connection放在Threadlocal里的话,那么我们的程序只需要一个connection连接数据库就行了,每个线程都是用的connection的一个副本,那为什么还有必要要数据库连接池呢?

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

疑问2:既然ThreadLocal当当前线程中没有时去新建一个新的,有的话就用当前线程中的,那数据库连接池已经有了这种功能啊,还要ThreadLocal干什么?

由于请求中的一个事务涉及多个 DAO 操作,而这些 DAO 中的 Connection 不能从连接池中获得,如果是从连接池获得的话,两个 DAO 就用到了两个Connection,这样的话是没有办法完成一个事务的。DAO 中的 Connection 如果是从 ThreadLocal 中获得 Connection 的话那么这些 DAO 就会被纳入到同一个 Connection 之下。当然了,这样的话,DAO 中就不能把 Connection 给关了,关掉的话,下一个使用者就不能用了。


ThreadLocal与同步机制

在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本(每个线程创建一个,不是什么对象的拷贝或副本),从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。


框架中使用ThreadLocal解决线程安全问题

我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因此有状态的Bean就可以在多线程中共享了。一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程。同一线程贯通三层这样你就可以根据需要,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有关联的对象引用到的都是同一个变量。

我们在使用Hibernate的时候,经常会使用到currentSession(),而Hibernat是这样使用ThreadLocal的。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public static final ThreadLocal session = new ThreadLocal();     
  2.     public static Session currentSession() {  
  3.         Session s = (Session)session.get();   
  4.         // open a new session,if this session has none    
  5.         if(s == null){     
  6.               s = sessionFactory.openSession();   
  7.               session.set(s);     
  8.         }     
  9.           return s;     
  10.     }     

 
(1)初始化一个ThreadLocal对象,ThreadLocal有三个成员方法 get()、set()、initialvalue()。 如果不初始化initialvalue,则initialvalue返回null。(2)session的get根据当前线程返回其对应的线程内部变量,也就是我们需要的net.sf.hibernate.Session(相当于对应每个数据库连接).多线程情况下共享数据库链接是不安全的。ThreadLocal保证了每个线程都有自己的s(数据库连接)。(3)如果是该线程初次访问,自然,s(数据库连接)会是null,接着创建一个Session,具体就是行6。 (4)创建一个数据库连接实例 s (5)保存该数据库连接s到ThreadLocal中。 (6)如果当前线程已经访问过数据库了,则从session中get()就可以获取该线程上次获取过的连接实例。 
 

在自己的项目中使用ThreadLocal解决线程安全问题

这里已request对象为例,首先我们新建一个常量类用来新建ThreadLocal对象,代码如下:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class CommonConstant {  
  2.     public static final ThreadLocal<HttpServletRequest> requestTL = new ThreadLocal<HttpServletRequest>(); // 保存request的threadlocal  
  3. }  

接着新建一个Request工具类,用来获得当前的request或取得request里面的属性等,代码如下:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class RequestUtil {  
  2.     public static Object getAttribute(String name) {  
  3.         return CommonConstant.requestTL.get().getAttribute(name);  
  4.     }  
  5.     public static void setAttribute(String name, Object value) {  
  6.         CommonConstant.requestTL.get().setAttribute(name, value);  
  7.     }  
  8.     public static void removeAttribute(String name) {  
  9.         CommonConstant.requestTL.get().removeAttribute(name);  
  10.     }  
  11.     public static boolean containsKey(String name) {  
  12.         Object value = getAttribute(name);  
  13.         if (value != null) {  
  14.             return true;  
  15.         }  
  16.         return false;  
  17.     }  
  18.     public static boolean notContainsKey(String name) {  
  19.         Object value = getAttribute(name);  
  20.         if (value == null) {  
  21.             return true;  
  22.         }  
  23.         return false;  
  24.     }  
  25.     public static HttpServletRequest getRequest() {  
  26.         return CommonConstant.requestTL.get();  
  27.     }  
  28.     public static String getParameter(String name) {  
  29.         return CommonConstant.requestTL.get().getParameter(name);  
  30.     }  
  31. }  

接着最重要的就是什么时候将这个request放入当前线程,比如在Servlet中当然可以在dosomething(HttpServletRequest request, HttpServletResponse response){}这样的方法里,当然也可以再一些拦截器,过滤器的时候进行设置,如Spring的preHandle(HttpServletRequest request, HttpServletResponse response, Object handler),代码如下:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public boolean preHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {  
  2.         CommonConstant.requestTL.set(request);  
  3.     }  

那么接下来在需要的地方如Service层中就可以这样获取到当前线程的request了,代码如下:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. RequestUtil.getRequest();  
  2. RequestUtil.getAttribute(name);  
  3. RequestUtil.getParameter(name);  
  4. //如果没有RequestUtil,那么就先取得request  
  5. HttpServletRequest request = CommonConstant.requestTL.get();  

ThreadLocal简单实现

大概思路就是ThreadLocal对象中有一个map,map中保存的键值对的key是当前线程,值是线程局部变量的值。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class ThreadLocal {   
  2.     private Map values = Collections.synchronizedMap(new HashMap());  
  3.   
  4.     public Object get() {  
  5.         Thread curThread = Thread.currentThread();  
  6.         Object o = values.get(curThread);  
  7.         if (o == null && !values.containsKey(curThread)) {  
  8.             o = initialValue();  
  9.             values.put(curThread, o);  
  10.         }  
  11.         return o;  
  12.     }  
  13.   
  14.     public void set(Object newValue) {  
  15.         values.put(Thread.currentThread(), newValue);  
  16.     }  
  17.   
  18.     public Object initialValue() {  
  19.         return null;  
  20.     }  
  21. }  
这个实现和JDK的总体思路类似,但存在很多问题。因为用 Thread 对象做 values 映射表中的key将导致无法在线程退出后对 Thread 进行垃圾回收,而且也无法存储多个线程局部变量。从JDK5.0的源码来看,并非在ThreadLocal中有一个Map,而是在每个Thread中存在这样一个Map,具体是ThreadLocal.ThreadLocalMap。当用set时候,往当前线程里面的Map里 put 的key是当前的ThreadLocal对象。而不是把当前Thread作为Key值put到ThreadLocal中的Map里。 

ThreadLocal源代码实现

首先我们来看看JDK中的源码,部分源码如下:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class ThreadLocal<T> {  
  2.     protected T initialValue() {  
  3.         return null;  
  4.     }  
  5.     public ThreadLocal() {  
  6.     }  
  7.     public T get() {  
  8.         Thread t = Thread.currentThread();  
  9.         ThreadLocalMap map = getMap(t);  
  10.         if (map != null) {  
  11.             ThreadLocalMap.Entry e = map.getEntry(this);  
  12.             if (e != null)  
  13.                 return (T)e.value;  
  14.         }  
  15.         return setInitialValue();  
  16.     }  
  17.     private T setInitialValue() {  
  18.         T value = initialValue();  
  19.         Thread t = Thread.currentThread();  
  20.         ThreadLocalMap map = getMap(t);  
  21.         if (map != null)  
  22.             map.set(this, value);  
  23.         else  
  24.             createMap(t, value);  
  25.         return value;  
  26.     }  
  27.     public void set(T value) {  
  28.         Thread t = Thread.currentThread();  
  29.         ThreadLocalMap map = getMap(t);  
  30.         if (map != null)  
  31.             map.set(this, value);  
  32.         else  
  33.             createMap(t, value);  
  34.     }  
  35.      public void remove() {  
  36.          ThreadLocalMap m = getMap(Thread.currentThread());  
  37.          if (m != null)  
  38.              m.remove(this);  
  39.      }  
  40.     ThreadLocalMap getMap(Thread t) {  
  41.         return t.threadLocals;  
  42.     }  
  43.     void createMap(Thread t, T firstValue) {  
  44.         t.threadLocals = new ThreadLocalMap(this, firstValue);  
  45.     }  
  46. }  

从public void set(T value){}可以看出在设置当前线程的线程局部变量时,首先获得当前的线程,接着获取一个与当前线程关联的ThreadLocalMap对象(ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取,可以用常用的Map对象来理解),如果该ThreadLocalMap对象存在,则将当前线程局部变量和其值放进map,否则的话新建一个map,并使线程的threadLocals为新的map的引用。而当我们通过public T get(){}获取当前线程的线程局部变量时,如果当前线程有相应的ThreadLocalMap对象,则已线程局部变量为键,取出其值,如果map不存在,则通过setInitialValue();将初始化值也就是null设置到map中,并返回该初始化值。

这里我想强调的是每一个Thread对象中有一个ThreadLocal.ThreadLocalMap threadLocals对象引用,指向的是一个ThreadLocalMap对象,该map用来存储一些键值对,如<requestTL,request>;<reponseTL,reponse>,键值对中前一个为定义的线程局部变量,后一个为具体保存的变量。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public static final ThreadLocal<HttpServletRequest> requestTL = new ThreadLocal<HttpServletRequest>(); // 保存request的threadlocal  
  2. public static final ThreadLocal<HttpServletResponse> responseTL = new ThreadLocal<HttpServletResponse>(); // 保存response的threadlocal  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值