这是我参与更文挑战的第9天,活动详情查看: 更文挑战java
ThreadLocal在上篇文章里介绍了,他是干什么的,具体的使用方法,还有在源码层次分析了一下它的原理,连接在上边↑安全
可是ThreadLcoal使用不当,也是回发生内存泄漏和线程不安全的markdown
咱们先来一个不使用ThreadLcoal的例子,分析一下内存占用状况并发
咱们先把堆内存最大值设为256M!ide
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呢post
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");
}
复制代码
此次咱们再看看一下内存的使用状况this
哦吼,内存居然达到了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持有呢,因此必定会发生内存泄漏
仍是老样子,错误的使用方法,也是回致使线程不安全的,
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对象就被五个线程共享了,也就不安全了,本来是一个线程一个房间的,可是上边的用法致使,五个线程拿到的不是五个房间而是五个同样的门牌号,操做的时候是在一间房子里操做的、
ThreadLcoal的源码分析内存泄漏的缘由,这样咱们使用的时候就能很大限度的避免内存泄漏,介绍了ThreadLocal不安全的一种用法!若有错误之处,请大佬们在评论区指出!大佬们一键三连!