一,概念
它是一个线程的副本变量工具类,[工具类]java.lang.ThreadLocal.,当各个线程依赖同一个对象的时候,将这个对象复制隔离起来,互不影响的骚操作。也就是说,它应该是一个容器是吧,它不是一个线程!
为什么要这样操作呢?如果说各个线程的操作互不影响,加锁又消耗效率,那么复制一份就是解决办法,由于java是引用类型,所以复制不简单,可能要序列化才能高度,所以这里推出一个工具类。
ThreadLocal接口有四个方法:set get,remove,initialValue(下面有代码)。四个方法,在jdk1.5之后,该类还支持泛型。
二,实现原理
它的类部有一个map,key对应每一个ThreadLocal实例(也就是this),value对应存放的值Object。
参考链接: https://www.jianshu.com/p/98b68c97df9b
每个Thread 类中有一个成员名为:ThreadLocals ,它是ThreadLocal的内部类:ThreadLocalMap。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal的内部类,
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
....
虽然map是Thread的成员但是它自己无法操作,而是由ThreadLocal进行管理,而且一个ThreadLocal里面一个线程只能放一个值,如果要放多个值,那么就需要多个ThreadLocal实例(因为Key就是线程标识,所以,第二次放的时候就会覆盖。)---》ThreadLocal提供的方法,get,set那么就是针对map(内部类)操作的,
为什么要这样呢????
因为这是内部类的特性,ThreadLocalMap所以属性都是私有的,外部的类只能引用,但是无法访问,访问权限只有本类才有,这里就有第三方的味道哦,参考链接: https://blog.csdn.net/zzg1229059735/article/details/82715741 说的很清楚。
threadlocal的操作方式:get()
public T get() {
Thread t = Thread.currentThread(); //获得线程中的 map.
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
//------------
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//------------
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);
}
//------------
protected T initialValue() {
return null;}
set()
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
用例子来讲解三者之间的关系:用户就是Thread,而ThreadLocals(也就是map容器) 比如为银行卡,(银行卡里面存的钱,嘿嘿),而ThreadLocal比喻为银行,你拿着卡,什么都不能做(抛开网络支付什么的),你想要往卡里面存钱,只能去银行办理,你把卡给银行,钱给银行,银行帮你把钱存进去。那么这里就规定了,一个银行只能办理一个卡,如果你想要多个卡,那么就需要多个银行。一个银行卡签输了一个银行的Id.还有用户持有者的信息。
银行就可以设置为单例的,或者静态的。不用重复生成。而卡是需要银行办理new出来的。
所以这里把你的钱存进去,别人就看不到,拿不到, 就安全了,对于变量来说,也是一个道理,必须要要有线程的签名银行才进行操作,别的线程是无法访问的。
ThrealLocalMap 中的map 是自己实现的,没有使用jdk的hashMap,它使用Entry类来存放一个节点,使用数组+哈希来形成检索方式(手动实现),从上面代码我们可以知道,entry也是一个内部类,它使用继承+泛型来实现键值对的操作。
它是如何解决hash冲突的,由于它只是一个数组,而不是在但节点使用链表,所以它使用传统的解决hash方式,移位探索,而且还是一个一个的移动的。
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
应用场景: 解决数据库连接、Session管理,每个线程访问数据库都应当是一个独立的Session会话,如果多个线程共享同一个Session会话,有可能其他线程关闭连接了,当前线程再执行提交时就会出现会话已关闭的异常,导致系统异常。此方式能避免线程争抢Session,提高并发下的安全性。
参考链接:https://zhuanlan.zhihu.com/p/82737256
存在的问题:内存泄漏。什么是内存遗漏,在gc算法里面提到,本来应该被回收的对象,没有回收,不应该回收的,被回收了,这就出现问题。
ThreadLocal为什么会造成内存遗漏?:
ThreadLocalMap中的Entry的key是一个弱引用,所以,作为Key的ThreadLocal没有强引用,会被垃圾回收,下次使用的时候,就出现了Null异常,
如何解决:1.把ThreadLocal声明为Static,就不会被回收了。
2.使用后,即使进行remove移除。