Java进阶 ——— Java多线程(四)之多线程局部变量TreadLocal

在了解ThreadLocal之前,一定要确定一个概念:ThreadLocal不是用来解决共享对象的多线程访问问题的
那么ThreadLocal在多线程的作用是什么呢?从下面几个方面来了解

ThreadLocal的作用

ThreadLocal可以理解为:线程局部变量, 是每一个线程所单独持有的。其他线程不能对其进行访问, 通常是类中的 private static 字段,是对该字段初始值的一个拷贝,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联,

在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同一个变量,显然是不行的,并且我们也知道volatile这个关键字也是不能保证线程安全的。当然我们可以使用synchorinized 关键字来为此变量加锁,进行同步处理,从而限制只能有一个线程来使用此变量,但是加锁会大大影响程序执行效率,此外我们还可以使用ThreadLocal来解决对某一个变量的访问冲突问题。

当使用ThreadLocal维护变量的时候 为每一个使用该变量的线程提供一个独立的变量副本,即每个线程内部都会有一个该变量,这样同时多个线程访问该变量并不会彼此相互影响,因此他们使用的都是自己从内存中拷贝过来的变量的副本, 这样就不存在线程安全问题,也不会影响程序的执行性能。

深入了解ThreadLocal使用

先不去分析源码,仅仅写个例子,根据例子学习ThreadLocal的简单使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ThreadTest {
//定义一个全局ThreadLocal
public static ThreadLocal<String> locals= new ThreadLocal<String>();
//开启三个线程
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塞值,再打印出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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();
}
}
}

查看运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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的使用,接下来肯定要看看源码,分析内部实现方式。

就从ThreadLocal 几个主要方法来学习

1
2
3
public T get()
public void set(T value)
public void remove()

get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本

首先我们来看一下ThreadLocal类是如何为每个线程创建一个变量的副本的。

先来看get方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 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) {
//然后接着下面获取到<key,value>键值对,注意这里获取键值对传进去的是 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();
}

仔细看看每一步的操作:

  • getMap(t)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /**
    * 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是什么

1
2
3
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap是ThreadLocal类的一个内部类,我们继续取看ThreadLocalMap的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键值

如果getMap为null,则返回setInitialValue()

  • setInitialValue()方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /**
    * 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;
    }
  • createMap(t, value)的实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * 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

文章目录
  1. 1. ThreadLocal的作用
  2. 2. 深入了解ThreadLocal使用
  3. 3. ThreadLocal的理解
    1. 3.1. 先来看get方法:
    2. 3.2. 总结
    3. 3.3. 参考
|