您的位置:首页 > 其它

ThreadLocal与InheritableThreadLocal的使用

2015-12-29 14:14 357 查看
       ThreadLocal,线程本地存储,为变量在每个线程中都创建了一个副本,那么每个线程可以独立地改变和访问自己的副本变量,而不会影响其它线程所对应的副本变量。从线程的角度看,目标变量就像是线程的本地变量,这也是类名中“Local”所要表达的意思。

       ThreadLocal不是用来解决对象共享访问问题的,而是提供了保持对象的方法和避免参数传递的对象访问方式。每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象。

       在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个 threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本。初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对 Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为
value,存到threadLocals。然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

ThreadLocal类的定义:public class ThreadLocal<T>

get()方法是用来获取ThreadLocal在当前线程中保存的变量副本
public T get() {
Thread t = Thread.currentThread();//取得当前线程
ThreadLocalMap map = getMap(t);// 获取该线程对应的ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//获取到<key,value>键值对,注意这里获取键值对传进去的是this,而不是当前线程t。
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
如果获取成功,则返回value值。如果map为空,则调用setInitialValue方法并返回value。

这里的getMap,是调用当期线程t,返回当前线程t中的一个成员变量threadLocals。

    ThreadLocalMap getMap(Thread t) {

        return t.threadLocals;

    }

在Thread类中会发现有threadLocals与inheritableThreadLocals两个成员变量,都是ThreadLocal.ThreadLocalMap类。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocalMap这个类是ThreadLocal类的一个内部类:
public class ThreadLocal<T> {
。。。。。。
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
。。。。。。
}
可以看出ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键值。

再看setInitialValue方法:
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;
}

protected T initialValue() {
return null;
}

void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
在setInitialValue方法中,会调用protected修饰的initialValue()方法, 默认情况下,initialValue方法返回的是null。所以在进行get之前,必须先set,否则会报空指针异常,如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。

set方法就比较简单了:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
总的来说,实际上是通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中,threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,这样每个线程中可有多个threadLocal变量。

简单示例:
public class ThreadLocalTest {
ThreadLocal<StringBuilder> a = new ThreadLocal<>();
ThreadLocal<String> b = new ThreadLocal<String>() {
protected String initialValue() {
return Thread.currentThread().getName() + "---zero";
}
};

public static void main(String[] args) throws InterruptedException {
final ThreadLocalTest test = new ThreadLocalTest();

System.out.println(test.a.get());
System.out.println(test.b.get());

Thread thread1 = new Thread() {
public void run() {
test.a.set(new StringBuilder("hello"));
System.out.println(test.a.get());
System.out.println(test.b.get());
};
};
thread1.start();
thread1.join();

System.out.println(test.a.get());
System.out.println(test.b.get());
}
}
运行结果:
null
main---zero
hello
Thread-0---zero
null
main---zero


       InheritableThreadLocal类继承于ThreadLocal类,所以它具有ThreadLocal类的特性,但又是一种特殊的 ThreadLocal,其特殊性在于InheritableThreadLocal变量值会自动传递给所有子线程,即在创建子线程时,子线程会接收所有可继承的线程局部变量的初始值,以获得父线程所具有的值。而普通ThreadLocal变量不行。

       如果一个子线程调用InheritableThreadLocal的get(),那么它将与它的父线程看到同一个对象。为保护线程安全性,应该只对不可变对象(一旦创建,其状态就永远不会被改变的对象)使用InheritableThreadLocal,因为对象被多个线程共享。

简单示例:

public class InheritableThreadLocalTest {
InheritableThreadLocal<StringBuilder> a = new InheritableThreadLocal<>();
InheritableThreadLocal<String> b = new InheritableThreadLocal<String>();

public static void main(String[] args) throws InterruptedException {
final InheritableThreadLocalTest test = new InheritableThreadLocalTest();

test.a.set(new StringBuilder("main---hello"));
test.b.set("main---zero");
System.out.println(test.a.get());
System.out.println(test.b.get());

Thread thread1 = new Thread() {
public void run() {
System.out.println(test.a.get());
System.out.println(test.b.get());
StringBuilder a = test.a.get().append("---subThread");
test.a.set(a);
test.b.set("subThread--zero");
System.out.println(test.a.get());
System.out.println(test.b.get());
};
};
thread1.start();
thread1.join();

System.out.println(test.a.get());
System.out.println(test.b.get());
}
}运行结果:
main---hello
main---zero
main---hello
main---zero
main---hello---subThread
subThread--zero
main---hello---subThread
main---zero
可以看出,如果InheritableThreadLocal存储的是可变性(mutable)的对象,如StringBuilder,对于主线程设置的值子线程可以通过get函数获取,但子线程调用set函数设置新值后,对主线程没有影响,对其它子线程也没有影响,只对自己可见,但如果子线程先get获取再修改对象的属性,那么这个修改对主线程和其它子线程是可见的,因为共享的是同一个引用。为了保护线程的安全性,一般建议只传递不可变(Immuable)对象,即没有状态的对象。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: