一、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());
}
}
}
}