前言
java内存模型
java虚拟机,模仿真实的计算机结构,为java中各个变量、实例分配内存空间。主要有:
- 方法区:类信息、常量、静态变量、即时编译器编译后的代码数据。方法区包括运行时常量池:字面量、符号引用
- 堆:线程共享的内存区域,存放对象实例
- 程序计数器:
- 虚拟机栈:线程私有数据区域,每个方法执行时都会创建一个栈帧,存储方法的变量表、操作数栈、动态链接、返回值、返回地址等信息。
- 本地方法栈:线程私有数据区域,与native代码相关。
ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。
虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。
ThreadLocal类
ThreadLocal是如何为每个变量建立副本的。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
获取当前线程的ThreadLocalMap,获取成功则返回value值,失败则调用setInitialValue()。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
threadLoacls是一个ThreadLoaclMap类型,他是ThreadLoacl类的内部类:
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;
}
}
....
}
Entry类是ThreadLocalMap的内部类,且继承自WeakReference,并且使用ThreadLoacl类型作为键值。
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;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
- 在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)
- 在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
- 然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。
通俗说明就是,每个线程在创建的时候都会创建自己ThreadLocal类型的私有对象,通过这个私有对象作为Thread里的map的key值,可以找出对应的value,从而达到线程间value复制且不相互影响的作用。
网友评论