这是我参与更文挑战的第9天,活动详情查看: 更文挑战
ThreadLocal在上篇文章里介绍了,他是干什么的,具体的使用方法,还有在源码层次分析了一下它的原理,链接在上边↑
但是ThreadLcoal使用不当,也是回发生内存泄漏和线程不安全的
1、ThreadLcoal内存泄漏分析
我们先来一个不使用ThreadLcoal的例子,分析一下内存占用情况
我们先把堆内存最大值设为256M!
public class ThreadLocalOOM {
private static final int TASK_LOOP_SIZE = 500;
final static ThreadPoolExecutor poolExecutor
= new ThreadPoolExecutor(5, 5, 1,
TimeUnit.MINUTES,
new LinkedBlockingQueue<>());
static class LocalVariable {
private byte[] a = new byte[1024*1024*5];/*5M大小的数组*/
}
ThreadLocal<LocalVariable> localVariable;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < TASK_LOOP_SIZE; ++i) {
poolExecutor.execute(new Runnable() {
public void run() {
new LocalVariable();
System.out.println("use local varaible");
}
});
Thread.sleep(100);
}
System.out.println("pool execute over");
}
}
我们运行玩main方法然后用Java visual Vm走一下看一下堆内存的情况
我们发现堆内存的大小,最高也就25M左右,完全是符合我们的预期的,但是我们上班的代码还没有用ThreadLcoal呢
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < TASK_LOOP_SIZE; ++i) {
poolExecutor.execute(new Runnable() {
public void run() {
ThreadLocalOOM oom=new ThreadLocalOOM();
oom.localVariable=new ThreadLcoal<>();
oom.localVariable(new LocalVariable());
System.out.println("use local varaible");
}
});
Thread.sleep(100);
}
System.out.println("pool execute over");
}
这次我们再看看一下内存的使用情况
哦吼,内存竟然达到了150M甚至达到了200M
XDM,难倒我们加了ThreadLcoal以后,占用内存如此之大吗?
肯定是发生了内存泄露了,我们再加一句代码,再观察一下
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < TASK_LOOP_SIZE; ++i) {
poolExecutor.execute(new Runnable() {
public void run() {
ThreadLocalOOM oom=new ThreadLocalOOM();
oom.localVariable=new ThreadLcoal<>();
oom.localVariable(new LocalVariable());
System.out.println("use local varaible");
oom.localVariable.remove();
}
});
Thread.sleep(100);
}
System.out.println("pool execute over");
}
nice运行中内存大小的波动恢复到没有用ThreadLocal之前了,这是怎么回事呢?
我们再去ThreadLcoal的源码里看一下
我们看到ThreadLcoal里的ThreadLocalMap里的Entry的ThreadLcoal也就是key是一个弱引用,不知道Java四种引用是咋回事的XDM,去看Java四种引用的辨析,开头有链接
给XDM画张图就知道咋回事了
我们在开头就把堆大小设成了256
然后ThreadLcoal又是弱引用,所以在垃圾回收的时候会把ThreadLocal给回收掉,key也就回收掉了,但是我们的当前线程还引用了Map,Map还引用到了Entry,所以挡ThreadLocal被回收掉之后,value就不能通过ThreadLocal来访问了,所以就剩下了
这时候又有XDM说了,我们循环了500次,按这样说那我们的内存泄漏的大小应该比200M要大,为什么只到200M呢?
我们再去看ThreadLcoalMap的set方法和get方法
它会把 key为null的 Entry给清除一下,只不过这个方法在set的时候并不是每次都执行,也就是说回收的不及时,所以造成了一定程度上的内存泄漏
我们再去看remove方法
哦吼!remove出克e.clean之后也调用了清除key为null的Entry、
如果我们的Entry里的key用的是强引用会发生什么,如果是强引用,就会发生更多的内存泄漏,threadLocal的引用为null的话,因为是强引用threadlocal在垃圾回收的时候,虽然它和栈里的引用断了,但是他所在的Entry还被ThreadLocalMap持有呢,所以一定会发生内存泄漏
2、ThreadLcoal的不安全性
还是老样子,错误的使用方法,也是回导致线程不安全的,
public class ThreadLocalUnsafe implements Runnable {
public static Number number = new Number(0);
public void run() {
//每个线程计数加一
number.setNum(number.getNum()+1);
//将其存储到ThreadLocal中
value.set(number);
SleepTools.ms(2);
//输出num值
System.out.println(Thread.currentThread().getName()+"="+value.get().getNum());
}
public static ThreadLocal<Number> value = new ThreadLocal<Number>() {
};
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(new ThreadLocalUnsafe()).start();
}
}
private static class Number {
public Number(int num) {
this.num = num;
}
private int num;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
@Override
public String toString() {
return "Number [num=" + num + "]";
}
}
}
上面的使用就是不安全的,如果对引用和对象,堆和栈不清楚的XDM,会犯这个错误,使用ThreadLocal的时候set的时候是一个引用,但是这五个线程他们操作的对象却是一个,这样的话,这个Number对象就被五个线程共享了,也就不安全了,原本是一个线程一个房间的,但是上边的用法导致,五个线程拿到的不是五个房间而是五个一样的门牌号,操作的时候是在一间房子里操作的、
3、总结
ThreadLcoal的源码分析内存泄漏的原因,这样我们使用的时候就能很大限度的避免内存泄漏,介绍了ThreadLocal不安全的一种用法!如有错误之处,请大佬们在评论区指出!大佬们一键三连!