這是我參與更文挑戰的第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不安全的一種用法!若有錯誤之處,請大佬們在評論區指出!大佬們一鍵三連!