ThreadLocal应用及原理
1. 使用Demo
public class Test1 {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static void main(String[] args) {
Test1 test1=new Test1();
for(int i=0;i<5;++i){
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
test1.setName(Thread.currentThread().getName()+"的数据");
System.out.println(Thread.currentThread().getName()+"的数据是"+test1.getName());
}
});
thread.setName("线程"+i);
thread.start();
}
}
}
/*打印结果:
线程1的数据是线程1的数据
线程0的数据是线程0的数据
线程2的数据是线程1的数据 //线程2先改了自己的工作内存,又刷新到了主内存的name的值。(只是一种情况)
线程3的数据是线程3的数据
线程4的数据是线程4的数据*/
一点都不想解释,懂的都懂。
下面是使用ThreadLocal的demo:
public class Test1 {
private String name;
static ThreadLocal<String> tlname=new ThreadLocal<>();
public static void main(String[] args) {
Test1 test1=new Test1();
for(int i=0;i<5;++i){
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
tlname.set(Thread.currentThread().getName()+"的数据");
System.out.println(Thread.currentThread().getName()+"的数据是"+tlname.get());
tlname.remove();
}
});
thread.setName("线程"+i);
thread.start();
}
}
}
/*打印结果:
线程2的数据是线程2的数据
线程1的数据是线程1的数据
线程0的数据是线程0的数据
线程3的数据是线程3的数据
线程4的数据是线程4的数据*/
2. 运用场景
比如开启多个线程执行service代码,由于数据库要用到事务(数据库里面的事务,不是Java里面的事务),而事务的逻辑是要在service层体现的,如(多线程执行service层的transfer方法)
想到最直接的方法,就是把service层的connection传到dao层,为了保证传入的是自己的service里面申请的connection,所以这里上了锁。上锁肯定不好啊,全都锁上了为什么还要多线程。。所以用ThredLocal。
至于为什么要保证是同一个connection:随便说其中一点,假如共享connection的话,若第一个线程里面抛出了异常,断掉了connection,剩下的线程就不能用了,这合理吗?
采用ThreadLocal,service层便不需要向dao层传connection,降低耦合度。也比上一种加锁的方法性能好。因为都是同一个线程的,所以不需要传connection。
3. 源码
public class ThreadLocal<T> {
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode =
new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
protected T initialValue() { //ThreadLocalMap的value初始值为null
return null;//protected修饰该方法很明显就是为了可以被子类重写
}
public ThreadLocal() {
}
//返回当前线程的map里面找到key==this对应的value,如果value=null,
//也许是key不存导致vaule=null,也许是被回收了(这里挖坑)
//则将<this,initialValue>加入map
public T get() {
Thread t = Thread.currentThread();//获取当前线程对象
ThreadLocalMap map = getMap(t);//获取当前线程的ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
//将<this,initialValue>加入map,并返回initialVaule.
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;
}
//将<this,value>加入到map
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//从map中将key==this的键值对移除
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
}
有几点比较重要的是:
- 每一个线程有一个ThreadLocalMap,map里面存的是<ThreadLocal,value>。
-
map里存储的entry数量变少,减少哈希冲突,现在键值对的个数取决于ThreadLocal的数量。假如是对每一个ThreadLocal维护一个<Thread,value>的map,键值对个数取决于Thread的数量,很明显Thread很可能远大于ThreadLocal的数量。
2.当一个线程销毁后,对应的ThreadLocalMap会不被别的对象引用 ,就会被当成垃圾,进而回收掉。
- ThreadLocalMap里面哈希冲突解决采用的方法是 开发地址-线性探测法.(d[i]=1)
4. 内存泄漏
ThreadLocalMap里的set/getEntry/remove方法中会调用expungeStaleEntry(),该方法会对key为null进行判断,key为null的话,也将value置为null。这意味着使用完ThreadLocal,CurrentThread已经运行的前提下,就算忘记调用remove方法,弱引用也比强引用可以多一层保障。弱引用的ThreadLocal会被回收,对应的value在下次一ThreadLocalMap调用set/get/remove任一方法时会被置为null,从而降低内存泄露的可能性。
-
采用弱引用了,也采用一层保障了,为什么还会内存泄漏呢?
使用线程池的话:一个线程使用了很多ThreadLocal,key也都不为空,该线程的这个任务执行完了,继续调用下一个任务,直到线程被销毁才可以进行回收。(而实际中之后的任务可能都用不到了。。,这样会长期占用大量内存,导致对一些需要内存的线程来说内存不够用,从而内存溢出) -
为什么Map存储value时不采用弱引用呢?
这问题好智障。value只被map引用,下一次垃圾收集一发生,value所引用的以及value就被回收了。
????
本文地址:https://blog.csdn.net/qq_42576687/article/details/109251846