Threadlocal是什么?有什么用?
个人学习,原文链接,感谢原文作者www.cnblogs.com/fsmly/p/110…
多个线程对同一个共享变量进行访问的时候容易出现问题。
为了保证线程安全,可以使用加锁这种方式来保证,也可以通过使用Threadlocal
当创建一个变量后,每个线程使用的都是线程本地自己的变量
ThreadLocal是JDK包提供的,它提供线程本地变量,
如果创建一乐ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,
操作的是自己本地内存中的变量,从而规避了线程安全问题
重点是:每个线程的本地变量不是存放在ThreadLocal实例中,而是放在调用线程的ThreadLocals变量里面(前面也说过,该变量是Thread类的变量)
public class ThreadLocalTest {
static ThreadLocal<String> strLocal = new ThreadLocal<>(); //threadlocal变量
static void print(String str){
System.out.println(str+" : " + strLocal.get());
strLocal.remove(); //删除此线程局部变量的当前线程值
}
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
strLocal.set("local1");
System.out.println("thread1: "+strLocal.get());
print("thread1");
System.out.println("after remove : " + strLocal.get());
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
strLocal.set("local2");
System.out.println("thread2: "+strLocal.get());
print("thread2");
System.out.println("after remove : " + strLocal.get());
}
});
//每个线程中的值互不影响
t1.start();
t2.start();
}
}
输出以下内容。可以看到,每个线程中的值都是互不影响的
thread1: local1
thread1 : local1
thread2: local2
thread2 : local2
after remove : null
after remove : null
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<?>> {
/** 与此ThreadLocal关联的值. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
每个Entry中包含一个Key-value,key为threadlocal,value为具体的对象。持有的是ThreadLocal的弱引用
ThreadLocal实现原理,首先看getMap方法
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
这个方法返回一个ThreadLocalMap类型的值,threadLocals是Thread类中的一个ThreadLocalMap变量,初始值为null
set、get、setInitialValue方法
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的Threadlocals变量
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;
}
}
//map为空的话设置初始值
return 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;
}
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的Threadlocals变量
ThreadLocalMap map = getMap(t);
//不为空设置值
if (map != null)
map.set(this, value);
//为空创建
else
createMap(t, value);
}
每个线程内部有一个名为threadLocals的成员变量,该变量的类型为ThreadLocal.ThreadLocalMap类型(类似于一个HashMap),其中的key为当前定义的ThreadLocal变量的this引用,value为我们使用set方法设置的值。每个线程的本地变量存放在自己的本地内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量就会一直存在(所以可能会导致内存溢出),因此使用完毕需要将其remove掉。
ThreadLcoal的继承性
同一个变量在父线程设置后,子线程是获取不到的。,因为每个threadlcoal存放的是当前线程的值。父线程设置的值,在子线程是不能获取的,如下。
public class ThreadLocalTest2 {
//创建一个threadlocal变量
public static ThreadLocal<String> strLocal = new ThreadLocal<>();
public static void main(String[] args) {
//主线程添加变量
strLocal.set("这个是主线程的变量");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程中的变量值: " + strLocal.get());
}
});
t1.start();
System.out.println("主线程中的变量值: " + strLocal.get());
}
}
//输出
//主线程中的变量值: 这个是主线程的变量
//子线程中的变量值: null
假设可以被子线程访问到,那么ThreadLcoal就无法保证线程安全了吗?应该就没有意义了。
InheritableThreadLocal
这个类可以支持子线程访问父线程的变量。 InheritableThreadLocals类通过重写getMap和createMap两个方法将本地变量保存到了具体线程的inheritableThreadLocals变量中,当线程通过InheritableThreadLocals实例的set或者get方法设置变量的时候,就会创建当前线程的inheritableThreadLocals变量。而父线程创建子线程的时候,ThreadLocalMap中的构造函数会将父线程的inheritableThreadLocals中的变量复制一份到子线程的inheritableThreadLocals变量中。 同样是上边的代码,将ThreadLocal类替换成InheritableThreadLocal类,可以看到输出是一样的。
关于内存泄漏
THreadLocalMap中的Entry的key使用的是ThreadLocal对象的弱引用,在没有其他地方对ThreadLoca依赖,ThreadLocalMap中的ThreadLocal对象就会被回收掉,但是对应的不会被回收,这个时候Map中就可能存在key为null但是value不为null的项,这需要实际的时候使用完毕及时调用remove方法避免内存泄漏。