在了解ThreadLocal之前,一定要确定一个概念:ThreadLocal不是用来解决共享对象的多线程访问问题的
那么ThreadLocal在多线程的作用是什么呢?从下面几个方面来了解
ThreadLocal可以理解为:线程局部变量, 是每一个线程所单独持有的。其他线程不能对其进行访问, 通常是类中的 private static 字段,是对该字段初始值的一个拷贝,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联,
在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同一个变量,显然是不行的,并且我们也知道volatile这个关键字也是不能保证线程安全的。当然我们可以使用synchorinized 关键字来为此变量加锁,进行同步处理,从而限制只能有一个线程来使用此变量,但是加锁会大大影响程序执行效率,此外我们还可以使用ThreadLocal来解决对某一个变量的访问冲突问题。
当使用ThreadLocal维护变量的时候 为每一个使用该变量的线程提供一个独立的变量副本,即每个线程内部都会有一个该变量,这样同时多个线程访问该变量并不会彼此相互影响,因此他们使用的都是自己从内存中拷贝过来的变量的副本, 这样就不存在线程安全问题,也不会影响程序的执行性能。
先不去分析源码,仅仅写个例子,根据例子学习ThreadLocal的简单使用。
public class ThreadTest {
//定义一个全局ThreadLocal
public static ThreadLocal locals= new ThreadLocal();
//开启三个线程
public void threadLocalTest(){
ThreadLocalTest test1 = new ThreadLocalTest("AA");
ThreadLocalTest test2 = new ThreadLocalTest("BB");
ThreadLocalTest test3 = new ThreadLocalTest("CC");
test1.start();
test2.start();
test3.start();
}
}
在线程里往ThreadLocal塞值,再打印出
public class ThreadLocalTest extends Thread {
private int a = 5;
public ThreadLocalTest(String name) {
super(name);
}
@Override
public void run() {
super.run();
try {
for (int i = 0; i < 5; i++) {
a += 1;
ThreadTest.locals.set(a + "");
Log.e(this.getName(), "value ===== " + ThreadTest.locals.get());
Thread.sleep(300);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
查看运行结果
10-24 10:31:46.340 9307-9350/com.t9.news E/AA: value ===== 6
10-24 10:31:46.340 9307-9351/com.t9.news E/BB: value ===== 6
10-24 10:31:46.343 9307-9352/com.t9.news E/CC: value ===== 6
10-24 10:31:46.640 9307-9350/com.t9.news E/AA: value ===== 7
10-24 10:31:46.641 9307-9351/com.t9.news E/BB: value ===== 7
10-24 10:31:46.644 9307-9352/com.t9.news E/CC: value ===== 7
10-24 10:31:46.940 9307-9350/com.t9.news E/AA: value ===== 8
10-24 10:31:46.941 9307-9351/com.t9.news E/BB: value ===== 8
10-24 10:31:46.944 9307-9352/com.t9.news E/CC: value ===== 8
10-24 10:31:47.240 9307-9350/com.t9.news E/AA: value ===== 9
10-24 10:31:47.241 9307-9351/com.t9.news E/BB: value ===== 9
10-24 10:31:47.244 9307-9352/com.t9.news E/CC: value ===== 9
10-24 10:31:47.540 9307-9350/com.t9.news E/AA: value ===== 10
10-24 10:31:47.541 9307-9351/com.t9.news E/BB: value ===== 10
10-24 10:31:47.545 9307-9352/com.t9.news E/CC: value ===== 10
看到每个线程的里都有自己的String,并且互不影响----,不存在一个线程修改另一个线程中值得情况,对于同一个ThreadLocal对象而言,内部数据仅为自己独有,其他线程无法修改
了解了ThreadLocal的使用,接下来肯定要看看源码,分析内部实现方式。
就从ThreadLocal 几个主要方法来学习
public T get()
public void set(T value)
public void remove()
get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本
首先我们来看一下ThreadLocal类是如何为每个线程创建一个变量的副本的。
/**
* 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();
//然后getMap(t)方法获取到一个map,类型为ThreadLocalMap
ThreadLocalMap map = getMap(t);
//不为空
if (map != null) {
//然后接着下面获取到键值对,注意这里获取键值对传进去的是 this,而不是当前线程t。
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
//如果获取成功,则返回value值
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//否则调用setInitialValue方法返回value
return setInitialValue();
}
仔细看看每一步的操作:
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
//返回当前线程中的成员变量threadLocals
return t.threadLocals;
}
那么继续查看,进入Thread.class ,成员变量threadLocals是什么
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap是ThreadLocal类的一个内部类,我们继续取看ThreadLocalMap的实现:
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键值
如果getMap为null,则返回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);
//就是如果map不为空,就设置键值对,
if (map != null)
map.set(this, value);
else
//为空,再创建Map
createMap(t, value);
return value;
}
/**
* 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
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
创建ThreadLocalMap对象并赋值给Thread中的threadLocals
经过这一系列流程,ThreadLocal是为每个线程创建变量的副本就很清晰:
首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。
初始化Thread时,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。
1。每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
2。将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦
3.通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中
4.为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量
5.在进行get之前,必须先set,否则会报空指针异常;如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。
因为get方法中,getMap()默认为null,则返回setInitialValue(),setInitialValue()方法中, T value = initialValue() 默认返回null,最终会报空指针异常
http://www.cnblogs.com/dolphin0520/p/3920407.html