先考虑一个问题,对于用户的session用得比较多,一般就从request.getSession()OK了,但有时不方便能拿到request,像dwr登录、自定义标签等,更不可能依次当参数传下去。那好,定义一个全局的session变量,类似常量的处理。每次访问时重置一下就行了,问题好像解决了!
但考虑过并发问题吗?两个人登录,A置成自己的session了,B又置成他的session了,两人开始打架了,是不是。我们还能做什么吗?答案当然是肯定的。
我们引入ThreadLocal,多么伟大的发明啊!
首先它不是用来解决多线程并发共享一类问题的,它解决的是在一个线程中参数的传递。
下面来看一个hibernate中典型的ThreadLocal的应用:
private static final ThreadLocal threadSession = new ThreadLocal(); public static Session getSession() throws InfrastructureException { Session s = (Session) threadSession.get(); try { if (s == null) { s = getSessionFactory().openSession(); threadSession.set(s); } } catch (HibernateException ex) { throw new InfrastructureException(ex); } return s; }
想象一下这里的threadSession.get()和threadSession.set(s),都做了什么事?
一个猜想:有个全局的共享类,也许是个Map,key就是当前Thread线程,value是这里的s。
变量定义 |
key |
value |
操作 |
threadSession |
T1 |
session1 |
Get、set(s) |
T2 |
session2 |
Get、set(s) |
|
T3 |
Session3 |
Get、set(s) |
下面来看看ThreadLocal的实现原理(jdk1.5源码)
public class ThreadLocal<T> { /** * ThreadLocals rely on per-thread hash maps attached to each thread * (Thread.threadLocals and inheritableThreadLocals). The ThreadLocal * objects act as keys, searched via threadLocalHashCode. This is a * custom hash code (useful only within ThreadLocalMaps) that eliminates * collisions in the common case where consecutively constructed * ThreadLocals are used by the same threads, while remaining well-behaved * in less common cases. */ private final int threadLocalHashCode = nextHashCode(); /** * The next hash code to be given out. Accessed only by like-named method. */ private static int nextHashCode = 0; /** * The difference between successively generated hash codes - turns * implicit sequential thread-local IDs into near-optimally spread * multiplicative hash values for power-of-two-sized tables. */ private static final int HASH_INCREMENT = 0x61c88647; /** * Compute the next hash code. The static synchronization used here * should not be a performance bottleneck. When ThreadLocals are * generated in different threads at a fast enough rate to regularly * contend on this lock, memory contention is by far a more serious * problem than lock contention. */ private static synchronized int nextHashCode() { int h = nextHashCode; nextHashCode = h + HASH_INCREMENT; return h; } /** * Creates a thread local variable. */ public ThreadLocal() { } /** * Returns the value in the current thread's copy of this thread-local * variable. Creates and initializes the copy if this is the first time * the thread has called this 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) return (T)map.get(this); // Maps are constructed lazily. if the map for this thread // doesn't exist, create it, with this ThreadLocal and its // initial value as its only entry. T value = initialValue(); createMap(t, value); return value; } /** * Sets the current thread's copy of this thread-local variable * to the specified value. Many applications will have no need for * this functionality, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current threads' 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); } /** * 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); } ....... /** * 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 { ........ } }
这个ThreadLocalMap 类是ThreadLocal中定义的内部类,但是它的实例却用在Thread类中:
public class Thread implements Runnable { ...... /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; ...... }
存储关系示意图
|
key |
value |
操作 |
T1. ThreadLocalMap |
threadSession |
session 1 |
Get、set(s) |
T2. ThreadLocalMap |
threadSession |
session 2 |
Get、set(s) |
T3. ThreadLocalMap |
threadSession |
session 3 |
Get、set(s) |
可以扩展为
|
key |
value |
操作 |
T1. ThreadLocalMap |
threadSession |
session1 |
Get、set(s) |
T1. ThreadLocalMap |
threadV1 |
V1 |
Get、set(v) |
T1. ThreadLocalMap |
threadV2 |
V2 |
Get、set(v) |
|
|
|
|
T2. ThreadLocalMap |
threadSession |
session 2 |
Get、set(s) |
T3. ThreadLocalMap |
threadSession |
session 3 |
Get、set(s) |
它为每个线程创建了一个map,而不是全局共享的map。这样做的好处到底是什么?
下面举一个例子(特别感谢小魏同学)说明这个关系:我们要去住宾馆是要完成的任务(或请求),每个房间就是一个个的线程,并了房间我们把钱包放桌上,睡觉,带上钱包,离开宾馆。这里放包的桌子是一个ThreadLocalMap,我放那,再取走一定是我的。可以放钱包、放衣服、放鞋子都可以。
上面的例子同样可以解释一下它不能解决实际需要共享的变量的并发访问问题,如每个房间的人都要给前台打电话,那么还是会有占线的问题。
但要注意,不能乱用,如果你没有放钱包,就要去取钱包,有可能没有,或者取到别人的(因为钱包是不会真正“拿走”的)。什么东西都要先放那儿,再去取。
另外,jdk1.5的实现与我们上面的设想的不同,目前想到的一点,全局的map可能有并发访问的问题,而jdk的实现却解决了这个问题。