深入探讨java.lang.ThreadLocal类
一、概述
ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是一个ThreadLocalVariable(线程局部变量)。
变量值的共享可以使用
public static
变量的形式,所有的线程都使用同一个public static
变量. 如果想实现每一个每一个线程都有自己的共享变量该如何解决呢? JDK中提供的类ThreadLocal正是为了解决这样的问题.
类ThreadLocal主要解决的就是每个线程绑定自己的值,可以将 ThreadLocal类比喻成全局存放数据的盒子,盒子中可以存储每个线程的私有数据.
从线程角度看,只要线程是活动的并且 ThreadLocal实例时可访问的,那么每个线程都保持一个对其线程共享的私有变量副本的隐式引用,在线程消失之后,其线程的所有私有变量副本都会被垃圾回收(除非存在对这些变量副本的其他引用)。
通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。
ThreadLocal 是如何做到为每一个线程维护私有变量副本的呢?其实实现的思路很简单,在以前,底层实现是一个HashMap,key是当前线程,value是该实例。但是现在的设计思路改了!!现在的底层实现是Thread个HashMap,每个HashMap的key是这个ThreadLocal实例,value是那个对象的副本。
ThreadLocal在1.6版本后是在Thread类中有一个ThreadLocalMap的变量,然后用Thread.currentThread().threadLocals.get(this)来引用的各线程变量副本.
为什么这样搞呢?如果是原来的设计方案,那么在大型项目里有很多Thread和很多ThreadLocal的前提下,就会有ThreadLocal个HashMap,每个里面就有Thread个元素。在Thread很多的情况下性能会低。
还有一点,当一个线程停止时,对应的ThreadLocal副本都不存在了,可以销毁一个HashMap。但用第一种设计思路的话这些HashMap都在。
概括起来说,对于多线程资源共享问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,在不同线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而不互相影响。
二、API说明
ThreadLocal() -->创建一个线程本地变量
get() -->返回线程本地变量的当前线程副本中的值,如果第一次调用get()
方法则返回的值是null
protected T initialValue() --> 返回此线程本地变量的当前线程的初始值。最多在每次访问线程来获得每个线程局部变量时调用此方法一次,即线程第一次使用 get() 方法访问变量的时候。如果线程先于 get 方法调用 set(T) 方法,则不会在线程中再调用 initialValue 方法
set(T value) -->将线程本地变量的当前线程副本中的值设置为指定值.许多应用程序不需要此方法,它们只依赖于initialValue()方法来设置线程局部变量的值.
void remove() --> 移除此线程局部变量的值,这可能有助于减少线程局部变量的存储需求。如果再次访问此线程局部变量,那么在默认情况下它将拥有其 initialValue。
在程序中一般都重写initialValue方法,以给定一个特定的初始值。
三、代码实例
3.1 方法get()与null
public class ThreadLocalTest {
public static ThreadLocal t1 = new ThreadLocal();
public static void main(String[] args) {
if(t1.get() == null) {
System.out.println("从未放过值");
t1.set("我的值");
}
System.out.println(t1.get());
System.out.println(t1.get());
}
}
运行结果:
从未放过值
我的值
我的值
从上面的运行结果来看,第一次调用t1对象的get()
方法时返回的值是null,通过调用set()
方法赋值后顺利取出值并打印到控制台上.说明不同线程中的值是可以放入ThreadLocal类中进行保存的;
3.2 Hibernate的Session 工具类HibernateUtil
这个类是Hibernate官方文档中HibernateUtil类,用于Session管理;
public class HibernateUtil {
private static Log log = LogFactory.getLog(HibernateUtil.class);
private static final SessionFactory sessionFactory; //定义SessionFactory
static {
try {
// 通过默认配置文件hibernate.cfg.xml创建SessionFactory
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
log.error("初始化SessionFactory失败!", ex);
throw new ExceptionInInitializerError(ex);
}
}
//创建线程局部变量session,用来保存Hibernate的Session
public static final ThreadLocal session = new ThreadLocal();
/**
* 获取当前线程中的Session
* @return Session
* @throws HibernateException
*/
public static Session currentSession() throws HibernateException {
Session s = (Session) session.get();
// 如果Session还没有打开,则新开一个Session
if (s == null) {
s = sessionFactory.openSession();
session.set(s); //将新开的Session保存到线程局部变量中
}
return s;
}
public static void closeSession() throws HibernateException {
//获取线程局部变量,并强制转换为Session类型
Session s = (Session) session.get();
session.set(null);
if (s != null)
s.close();
}
}
在这个类中,由于没有重写ThreadLocal的initialValue()方法,则首次创建线程局部变量session其初始值为null,第一次调用currentSession()的时候,线程局部变量的get()方法也为null。因此,对session做了判断,如果为null,则新开一个Session,并保存到线程局部变量session中,这一步非常的关键,这也是public static final ThreadLocal session = new ThreadLocal()
所创建的对象session通过get()
获取的对象能强制转换为Hibernate Session对象的原因。
3.3 验证线程变量的隔离性
public class Tools {
public static ThreadLocal t1 = new ThreadLocal();
}
class ThreadA extends Thread {
@Override
public void run() {
try{
for (int i = 0; i < 20; i++) {
Tools.t1.set("ThreadA" + (i+1));
Thread.sleep(200);
System.out.println("ThreadA get Value = " +Tools.t1.get());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadB extends Thread {
@Override
public void run() {
try{
for (int i = 0; i < 20; i++) {
Tools.t1.set("ThreadB" + (i+1));
Thread.sleep(200);
System.out.println("ThreadB get Value = " + Tools.t1.get());
}
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Run {
public static void main(String[] args) {
try {
ThreadA a = new ThreadA();
ThreadB b= new ThreadB();
a.start();
b.start();
for (int i = 0; i < 20; i++) {
Tools.t1.set("Main" + (i+1));
Thread.sleep(200);
System.out.println("Main get Value = " + Tools.t1.get());
}
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
虽然三个线程都向t1对象中set()
数据值,但每个线程还是能取出自己的数据。
3.4 解决get() 返回null问题
public class ThreadLocalExt extends ThreadLocal{
/**
* Returns the current thread's "initial value" for this
* thread-local variable. This method will be invoked the first
* time a thread accesses the variable with the {@link #get}
* method, unless the thread previously invoked the {@link #set}
* method, in which case the {@code initialValue} method will not
* be invoked for the thread. Normally, this method is invoked at
* most once per thread, but it may be invoked again in case of
* subsequent invocations of {@link #remove} followed by {@link #get}.
*
*
This implementation simply returns {@code null}; if the
* programmer desires thread-local variables to have an initial
* value other than {@code null}, {@code ThreadLocal} must be
* subclassed, and this method overridden. Typically, an
* anonymous inner class will be used.
*
* @return the initial value for this thread-local
*/
@Override
protected Object initialValue() {
return "我是默认值 第一次get不再为null";
}
}
class run{
public static ThreadLocalExt t1 = new ThreadLocalExt();
public static void main(String[] args) {
if(t1.get() == null) {
System.out.println("从未放过值");
t1.set("我的值");
}
System.out.println(t1.get());
System.out.println(t1.get());
}
}
运行结果:
我是默认值 第一次get不再为null
我是默认值 第一次get不再为null
此案例仅仅证明main线程有自己的值,那其他线程是否会有自己的初始值呢?
3.5 再次验证线程变量的隔离性
public class Tools {
public static ThreadLocalExt t1 = new ThreadLocalExt();
}
class ThreadLocalExt extends ThreadLocal {
/**
* Returns the current thread's "initial value" for this
* thread-local variable. This method will be invoked the first
* time a thread accesses the variable with the {@link #get}
* method, unless the thread previously invoked the {@link #set}
* method, in which case the {@code initialValue} method will not
* be invoked for the thread. Normally, this method is invoked at
* most once per thread, but it may be invoked again in case of
* subsequent invocations of {@link #remove} followed by {@link #get}.
*
*
This implementation simply returns {@code null}; if the
* programmer desires thread-local variables to have an initial
* value other than {@code null}, {@code ThreadLocal} must be
* subclassed, and this method overridden. Typically, an
* anonymous inner class will be used.
*
* @return the initial value for this thread-local
*/
@Override
protected Object initialValue() {
return new Date().getTime();
}
}
class ThreadA extends Thread {
@Override
public void run() {
try{
for (int i = 0; i < 10; i++) {
System.out.println("在ThreadA 线程中取值 = " + Tools.t1.get());
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Run {
public static void main(String[] args) {
try {
for (int i = 0; i < 10; i++) {
System.out.println("在Main线程中取值 = " + Tools.t1.get());
Thread.sleep(100);
}
Thread.sleep(5000);
ThreadA a = new ThreadA();
a.start();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
子线程和父线程各有各自所拥有的值;