首页 > 代码库 > 深入理解ThreadLocal
深入理解ThreadLocal
一. ThreadLocal是什么?
ThreadLocal,顾名思义,它不是一个线程,而是线程的一个本地化对象。当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的初始化变量副本。所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。从线程的角度看,这个变量就像是线程的本地变量,这也是类名中“Local”所要表达的意思。
下面我们看一个例子:
public class ThreadLocalTest1 { private static Index num = new Index(); // 创建一个Index类型的本地变量 private static ThreadLocal<Index> local = new ThreadLocal<Index>() { @Override protected Index initialValue() { return num; // 注意这里,返回的是一个已经定义好的对象num,而不是new Index() --> 返回的不是副本 } }; public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[5]; for (int i = 0; i < 5; i++) { threads[i] = new Thread(new Runnable() { @Override public void run() { // 取出当前线程的本地变量,并累加5次 Index index = local.get(); for (int i = 0; i < 5; i++) { index.increase(); } // 重新设置累加后的本地变量 local.set(index); System.out.println(Thread.currentThread().getName() + " : " + index.num); } }, "Thread-" + i); } for (Thread thread : threads) { thread.start(); } } static class Index { int num; public void increase() { num++; } } }运行结果如下:
Thread-0 : 5 Thread-1 : 10 Thread-3 : 15 Thread-2 : 20 Thread-4 : 25我们会发现,每一次运行的结果都不一样,为什么会出现这样的情况?
上面的代码中,我们通过覆盖initialValue函数来给我们的ThreadLocal提供初始值,每个线程都会获取这个初始值的一个副本,而现在我们的初始值是一个定义好的对象,num是这个对象的引用。换句话说我们的初始值是一个引用,线程的引用副本指向的是同一个对象。如下图:
把方法改造一下:创建对象的副本而不是对象应用的副本
// 创建一个Index类型的本地变量 private static ThreadLocal<Index> local = new ThreadLocal<Index>() { @Override protected Index initialValue() { return new Index(); // 注意这里 } };
来看另外一个例子:
public class ThreadLocalTest2 { // 创建一个Integer型的线程本地变量 public static final ThreadLocal<Integer> local = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 0; } }; public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[5]; for (int i = 0; i < 5; i++) { threads[i] = new Thread(new Runnable() { @Override public void run() { // 获取当前线程的本地变量,然后累加5次 int num = local.get(); for (int i = 0; i < 5; i++) { num++; } // 重新设置累加后的本地变量 local.set(num); System.out.println(Thread.currentThread().getName() + " : " + local.get()); } }, "Thread-" + i); } for (Thread thread : threads) { thread.start(); } } }运行结果如下:
Thread-4 : 5 Thread-1 : 5 Thread-2 : 5 Thread-0 : 5 Thread-3 : 5我们可以看到,每个线程累加后的结果都是5,各个线程处理自己的本地变量值,线程之间互不影响。
二. ThreadLocal的接口方法
ThreadLocal从代码看其实很简单,每个线程调用set方法时,相当于往内部的Map中增加一条记录,key是各自的线程,value是各自线程调用set放进去的值。
public void set(Object value) 设置当前线程的线程局部变量的值;
public Object get() 返回当前线程所对应的线程局部变量;
public void remove() 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度;
- protected Object initialValue() 返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的默认实现直接返回一个null。
- 按照我们自己也可以实现一个简单的ThreadLocal:
public class SimpleThreadLocal { private Map map = Collections.synchronizedMap(new HashMap()); public void set(Object newValue) { // 键为线程对象,值为本线程的变量副本 map.put(Thread.currentThread(), newValue); } public Object get() { Thread currentThread = Thread.currentThread(); // 返回本线程对应的变量 Object obj = map.get(currentThread); // 如果在Map中不存在,放到Map中保存起来 if (obj == null && !map.containsKey(currentThread)) { obj = initialValue(); map.put(currentThread, obj); } return obj; } public void remove() { map.remove(Thread.currentThread()); } public Object initialValue() { return null; } }
三. ThreadLocal与同步机制的比较
ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序缜密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal为每一个线程提供一个独立的变量副本,从而隔离了多个线程对访问数据的冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的对象封装,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式:访问串行化,对象共享化。而ThreadLocal采用了“以空间换时间”的方式:访问并行化,对象独享化。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
四. 实际应用
1. hibernate中使用:
public class HibernateUtil { private static Log log = LogFactory.getLog(HibernateUtil.class); // 定义SessionFactory private static final SessionFactory sessionFactory; // 创建线程局部变量session,用来保存Hibernate的Session public static final ThreadLocal local = new ThreadLocal(); static { try { // 通过默认配置文件hibernate.cfg.xml创建SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory(); } catch (Throwable ex) { log.error("初始化SessionFactory失败!", ex); throw new ExceptionInInitializerError(ex); } } /** * 获取当前线程中的Session * @return Session * @throws HibernateException */ public static Session currentSession() throws HibernateException { Session session = (Session) local.get(); // 如果Session还没有打开,则新开一个Session if (session == null) { session = sessionFactory.openSession(); local.set(session); // 将新开的Session保存到线程局部变量中 } return session; } public static void closeSession() throws HibernateException { // 获取线程局部变量,并强制转换为Session类型 Session session = (Session) local.get(); local.set(null); if (session != null) session.close(); } }
2. 事务控制中使用:
将会另写博文记录
五. 参考文章:深入理解ThreadLocal