一、ThreadLocal是什么?
ThreadLocal的概念:线程本地变量,或线程本地存储。
ThreadLocal的作用:它主要是为变量在每个线程中都创建了一个副本,每个线程可以访问自己内部的副本变量。具体是通过它提供的get()和set()方法访问某个变量的线程都有自己的局部变量。
注意以下几点:
1. 每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
2. 将一个公用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。
3. 线程中的局部变量,是在线程中创建,然后调用ThreadLocal.set()方法设置进去的。
二、ThreadLocal的定义:
public class ThreadLocal<T>
三、ThreadLocal的成员变量:
//ThreadLocal实例hash值,用来区分不同实例
private final int threadLocalHashCode = nextHashCode();
//可以看作hash值的一个基值
private static AtomicInteger nextHashCode = new AtomicInteger();
//hash值每次增加量
private static final int HASH_INCREMENT = 0x61c88647;
nextHashCode()方法:
/** * Returns the next hash code. */ private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }它是返回一个hash code,同时对nextHashCode加上增量HASH_INCREMENT。也就是在初始化ThreadLocal的实例过程中,做的仅仅是将一个哈希值赋给实例的threadLocalHashCode,并生成下一个哈希值。
四、ThreadLocal的构造方法:
//只有一个无参构造函数
public ThreadLocal() {
}
五、ThreadLocal的保存线程局部变量方法:
ThreadLocal里还有一个ThreadLocalMap的内部类,它就是用来保存线程局部变量的Map。
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); } /** * 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); }
从上面的代码可以看到,set()方法是调用ThreadLocalMap.set()方法保存到ThreadLocalMap实例当中,但是ThreadLocalMap实例却不是保存在Thread中,它通过getMap()方法去取,当取不到的时候就去创建,但是创建之后却是保存在Thread实例中。可以看到Thread.java的代码有如下声明:
ThreadLocal.ThreadLocalMap threadLocals = null;
这种设计很巧妙,线程中有各自的map,而把ThreadLocal实例作为key,这样除了当线程销毁时相关的线程局部变量被销毁之外,还让性能提升了很多。
六、ThreadLocal的获取线程局部变量方法:
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) return (T)e.value; } return setInitialValue(); } /** * 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; } /** * 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 <tt>initialValue</tt> 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}. * * <p>This implementation simply returns <tt>null</tt>; if the * programmer desires thread-local variables to have an initial * value other than <tt>null</tt>, <tt>ThreadLocal</tt> must be * subclassed, and this method overridden. Typically, an * anonymous inner class will be used. * * @return the initial value for this thread-local */ protected T initialValue() { return null; }
可以看到在get()方法中,是通过当前线程获取map,再从map当中取得对象。如果取不到(如map为null或取到的值为null),则调用setInitialValue()方法对其设置初始值。setInitialValue()方法会调用initialValue()方法得到一个初始值(默认为null),然后当Thread中的map不为空时,把初始值设置进去,否则为它创建一个ThreadLocalMap对象(但不会调用map的set方法保存这个初始值),最后返回这个初始值。
七、ThreadLocal的删除线程局部变量方法:
remove()方法:
/** * Removes the current thread's value for this thread-local * variable. If this thread-local variable is subsequently * {@linkplain #get read} by the current thread, its value will be * reinitialized by invoking its {@link #initialValue} method, * unless its value is {@linkplain #set set} by the current thread * in the interim. This may result in multiple invocations of the * <tt>initialValue</tt> method in the current thread. * * @since 1.5 */ public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
八、ThreadLocal和Synchonized的比较:
1. 相同点:
ThreadLocal和Synchonized都用于解决多线程并发访问。
2. 不同点:
ThreadLocal与synchronized有本质的区别:
2.1.synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。
而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一
个对象,这样就隔离了多个线程对数据的数据共享。而synchronized却正好相反,它用于在多个线 程间通信时能够获得数据共享。
2.2. synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
3. ThreadLocal和线程同步机制相比有什么优势呢?
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
九、示例:
package com.mycom.test.threadlocal; /** * ThreadLocal * * @author guweiqiang */ public class TestNum { /** * 通过匿名的内部类覆盖ThreadLocal的initialValue()方法,指定初始值 */ private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){ public Integer initialValue(){ return 0; } }; /** * 获取下一个序列值 */ public Integer getNext(){ seqNum.set(seqNum.get()+1); return seqNum.get(); } public static void main(String[] args) { TestNum tn = new TestNum(); new TestClient(tn).start(); new TestClient(tn).start(); new TestClient(tn).start(); } private static class TestClient extends Thread{ private TestNum tn; public TestClient(TestNum tn){ this.tn = tn; } public void run(){ // 每个线程打出4个序列值 for(int i=0; i<4; i++){ System.out.println("thread["+Thread.currentThread().getName()+"]-->tn.next="+tn.getNext()); } } } }