0%

ThreadLocal线程本地存储

ThreadLocal线程本地存储

该类提供了线程局部变量,为每一个线程创建一个单独的变量副本,使得每个线程都可以独立的改变自己所拥有的变量副本,而不会影响其他线程所对应的副本,消除了竞争条件。

采用的以空间换时间的做法,在每个Thread里维护一个以开地址法实现的ThreadLocal.ThreadLocalMap,把数据隔离
线程本地存储根除了对变量的共享,每当线程访问threadLocals变量时,访问的都是各自线程自己的threadLocals变量。

Thread类

1
2
3
4
5
6
// ThreadLocalMap是一个定制化的HashMap,当前线程为key
// 线程的本地变量存储在线程的threadLocals变量中,并不是存储在ThreadLocal实例中

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

ThreadLocal源码逻辑

  • ThreadLocal对象通过Thread.currentThread()获取当前Thread对象
  • 当前Thread获取对象内部持有的ThreadLocalMap容器
  • 从ThreadLocalMap容器中用ThreadLocal对象作为key,操作当前Thread中的变量副本

提供了四个方法:

  • get() 返回此线程局部变量的当前线程副本中的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public T get() {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程实例的threadLocals
    ThreadLocalMap map = getMap(t);
    // 不为空
    if (map != null) {
    // 根据当前的ThreadLocal对象引用来取值
    ThreadLocalMap.Entry e = map.getEntry(this);
    if (e != null) {
    @SuppressWarnings("unchecked")
    T result = (T)e.value;
    return result;
    }
    }
    // 为空则设置key为当前的ThreadLocal对象,value为initialValue设置的初始值
    return setInitialValue();
    }
  • initialValue() 返回此线程局部变量当前线程的初始值,当线程第一次调用get()或set()方法时调用,并且只调用一次

  • remove() 移除此线程局部变量当前线程的值

1
2
3
4
5
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
  • set(T value) 将此线程局部变量的当前线程副本中的值设置为指定值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取该线程实例对象的threadLocals变量 getMap方法 return t.threadLocals;
    ThreadLocalMap map = getMap(t);
    // 不为空,key为当前的ThreadLocal对象引用,value为所存储的值
    if (map != null)
    map.set(this, value);
    else
    // 为空,则为threadLocals实例化对象
    createMap(t, value);
    }

    ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
    }

    void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
  • 还有一个静态内部类 ThreadLocalMap 提供了一种用键值对方式存储每一个线程的变量副本的方法,key为当前的ThreadLocal对象,value为对象线程的变量副本
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
28
29
30
31
32
33
34
35
36
public class MyThread implements Runnable {

@Override
public void run() {
for (int i = 0; i < 3; i++) {
ThreadLocalVariableHolder.increment();
System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalVariableHolder.get());
Thread.yield();
}
}
}

public class ThreadLocalVariableHolder {
private static ThreadLocal<Integer> myThreadLocal = new ThreadLocal<Integer>() {
// 初始值默认为null 设置初始值为0
protected Integer initialValue() {
return 0;
}
};

public static void increment() {
myThreadLocal.set(myThreadLocal.get() + 1);
}

public static int get(){
return myThreadLocal.get();
}

public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for(int i=0;i<5;i++){
executorService.execute(new MyThread());
}
executorService.shutdown();
}
}

执行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pool-1-thread-1:1
pool-1-thread-2:1
pool-1-thread-3:1
pool-1-thread-3:2
pool-1-thread-2:2
pool-1-thread-1:2
pool-1-thread-2:3
pool-1-thread-3:3
pool-1-thread-4:1
pool-1-thread-1:3
pool-1-thread-4:2
pool-1-thread-4:3
pool-1-thread-5:1
pool-1-thread-5:2
pool-1-thread-5:3

存在内存泄露问题,每次使用完ThreadLocal,都调用它的remove()方法,清除数据

InheritableThreadLocal类解决ThreadLocal继承性问题

InheritableThreadLocal是ThreadLocal的子类,该类提供了一个特性,可以让子线程访问在父线程中设置的本地变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class InheritableThreadLocal<T> extends ThreadLocal<T> {

protected T childValue(T parentValue) {
return parentValue;
}

// 获取ThreadLocalMap时获取的是该线程中的inheritableThreadLocals变量
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}

// 创建ThreadLocalMap时,使用的是inheritableThreadLocals变量而不是threadLocals变量
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}

如果一个线程是从其他某个线程中创建的,这个类将提供继承的值,如果一个线程A在线程局部变量中已有值,那么当线程A创建其他某个线程B时,线程B的线程局部变量将跟线程A是一样的,可以重写childValue()方法,该方法用来初始化子线程在线程局部变量中的值,使用父线程在线程局部变量中的值作为传入参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Thread实例化时的初始化过程
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// 省略无关代码

// 当前线程
Thread parent = currentThread();

// inheritThreadLocals为true 且 父线程的inheritableThreadLocals不为null
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
// 则设置子线程的inheritableThreadLocals,值为父线程的inheritableThreadLocals的值复制而来
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

}

javaweb中就是使用这种方式来使得子线程可以获取请求信息的

1
RequestContextHolder.setRequestAttributes(requestAttributes,true);

内存泄漏问题

1
2
3
4
5
6
7
8
9
10
// ThreadLocal中的Entry
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

在ThreadLocal.ThreadLocalMap中的key为ThreadLocal对象实例,这个Map中的key是一个弱引用,当把ThreadLocal对象实例置为null后,没有任何强引用指向ThreadLocal对象实例,所以ThreadLocal对象实例会被gc回收,但是Map中的value却不会被回收(此时entry是一个key为null,但是value不为null),因为存在一条从当前thread连接过来的强引用,只有当前thread结束之后,当前thread的强引用才会断开,此时Map中的value才会被gc回收。

但是在使用线程池的时候,由于线程是重复利用的,不会被回收,所以就可能出现内存泄漏

所以当使用完之后,需要调用ThreadLocal对象的remove()方法来删除掉