深入理解ThreadLocal
1、ThreadLocal是什么
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程对应的副本
2、ThreadLocal类提供的4个方法
- public void set(T value)
设置当前线程的线程局部变量的值
/** * 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); }
- public T get()
返回当前线程所对应的线程的局部变量
/** * 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) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
- public void remove()
将当前线程的局部变量的值删除
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
- protected T initialValue()
返回该线程局部变量的初始值,该方法是一个protected方法,显然是为了让子类覆盖而设计的,ThreadLocal中的缺省实现直接返回一个null
protected T initialValue() { return null; }
3、每个线程的变量副本存储在哪里
ThreadLocal有一个静态内部类ThreadLocalMap,该内部类保存了当前线程所对应的变量的副本(需要注意的是,变量是保存在线程中的,而不是保存在ThreadLocal变量中)
Thread类表示的当前线程中,有一个变量引用名是threadLocals,这个引用是在ThreadLocal类中createMap函数内初始化的
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
我们所使用的ThreadLocal变量的实际数据,通过get函数实际取值的时候,就是通过取出Thread类中threadLocals引用的map,然后从这个map中根据当前threadLocal作为参数,取出数据
4、变量副本是怎么从共享的那个变量复制出来的,ThreadLocal的初始值什么时候设置的?
准确的说,应该是,变量副本【每个线程中保存的那个map中的变量】是怎么声明和初始化的
从set函数可以看出,当前线程的ThreadLocalMap是在第一次调用set的时候创建map并设置相应的值的
5、一个线程声明了n(n>=2)个这样的局部变量threadLocal,那么在Thread类中的threadLocals是怎么存储的?
在ThreadLocal的set函数中可以看到,其中的map.set(this, value)把当前的ThreadLocal传入到map中作为键,也就是说,在不同线程的threadLocals变量中,都会有一个以你所声明的那个线程局部变量threadLocal作为键的key-value。假设你声明了n个这样的线程局部变量,那么在线程的ThreadLocalMap中就会有n个分别以你的线程局部变量作为key的键值对
6、应用场景
当很多线程需要多次使用同一个对象,并且需要该对象具有相同初始化值的时候最适合使用ThreadLocal
7、典型应用
ThreadLocal在Hibernate中的应用
private static final ThreadLocal threadSession = new ThreadLocal(); public static Session getSession() throws InfrastructureException { //首先判断当前线程中有没有放进去session Session s = (Session) threadSession.get(); try { //如果还没有,通过sessionFactory.openSession()来创建一个session,再将session放到线程中 //实际上是放到当前线程的ThreadLocalMap中,对于这个session的唯一引用就是当前线程中的那个ThreadLocalMap if (s == null) { s = getSessionFactory().openSession(); threadSession.set(s); } } catch (HibernateException ex) { throw new InfrastructureException(ex); } return s; }
试想如果不用ThreadLocal怎么来实现呢?可能就要在action中创建session,然后把session一个个传到service和dao中
或者自己定义一个静态的map,将当前thread作为key,创建的session作为值,put到map中
8、代码示例
8.1 例1
public class ThreadLocalTest { //创建一个Integer型的线程本地变量 public static final ThreadLocal<Integer> local = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 0; } }; public static void main(String[] args) { Thread[] threads = new Thread[5]; for (int i = 0; i < 5; i++) { new Thread(new Runnable() { @Override public void run() { //获取当前线程的本地变量,然后累加5次 int num = local.get(); for (int j = 0; j < 5; j++) { num++; } //重新设置累加后的本地变量 local.set(num); System.out.println(Thread.currentThread().getName() + ":" + local.get()); } }, "Thread-" + i).start(); } } }
运行后结果:
Thread-0:5
Thread-3:5
Thread-2:5
Thread-1:5
Thread-4:5
可以看到,每个线程累加后的结果都是5,各个线程处理自己的本地变量值,县城之间互不影响。
8.2 例2
public class ThreadLocalTest { private static Index num = new Index(); //创建一个Integer型的线程本地变量 public static final ThreadLocal<Index> local = new ThreadLocal<Index>() { @Override protected Index initialValue() { return num; } }; public static void main(String[] args) { Thread[] threads = new Thread[5]; for (int i = 0; i < 5; i++) { new Thread(new Runnable() { @Override public void run() { //获取当前线程的本地变量,然后累加5次 Index index = local.get(); for (int j = 0; j < 5; j++) { index.increase(); } //重新设置累加后的本地变量 local.set(index); System.out.println(Thread.currentThread().getName() + ":" + local.get().num); } }, "Thread-" + i).start(); } } static class Index { int num; public void increase() { num++; } } }
执行结果(每次运行结果都不一样):
Thread-0:5
Thread-1:20
Thread-3:25
Thread-4:15
Thread-2:10
原因:
ThreadLocal可以给一个初始值,而每个线程获得的是这个初始值的副本,而不是要创建对象引用的副本
public static final ThreadLocal<Index> local = new ThreadLocal<Index>() { @Override protected Index initialValue() { return new Index(); //注意这里 } };
改为以上方法后,正确输出结果:
Thread-0:5
Thread-2:5
Thread-4:5
Thread-3:5
Thread-1:5