Java併發編程---ThreadLocal的不安全性和內存泄漏分析

這是我參與更文挑戰的第9天,活動詳情查看: 更文挑戰java

Java併發編程---ThreadLocal源碼解析編程

辨析Java的四種引用,那我走?數組

ThreadLocal在上篇文章裏介紹了,他是幹什麼的,具體的使用方法,還有在源碼層次分析了一下它的原理,連接在上邊↑安全

可是ThreadLcoal使用不當,也是回發生內存泄漏和線程不安全的markdown

一、ThreadLcoal內存泄漏分析

咱們先來一個不使用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走一下看一下堆內存的狀況源碼分析

image.png

咱們發現堆內存的大小,最高也就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

image.png 哦吼,內存居然達到了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");
    }

複製代碼

image.png

nice運行中內存大小的波動恢復到沒有用ThreadLocal以前了,這是怎麼回事呢?

咱們再去ThreadLcoal的源碼裏看一下

image.png

咱們看到ThreadLcoal裏的ThreadLocalMap裏的Entry的ThreadLcoal也就是key是一個弱引用,不知道Java四種引用是咋回事的XDM,去看Java四種引用的辨析,開頭有連接

給XDM畫張圖就知道咋回事了

image.png 咱們在開頭就把堆大小設成了256

而後ThreadLcoal又是弱引用,因此在垃圾回收的時候會把ThreadLocal給回收掉,key也就回收掉了,可是咱們的當前線程還引用了Map,Map還引用到了Entry,因此擋ThreadLocal被回收掉以後,value就不能經過ThreadLocal來訪問了,因此就剩下了

這時候又有XDM說了,咱們循環了500次,按這樣說那咱們的內存泄漏的大小應該比200M要大,爲何只到200M呢?

咱們再去看ThreadLcoalMap的set方法和get方法

image.png

image.png

image.png

它會把 key爲null的 Entry給清除一下,只不過這個方法在set的時候並非每次都執行,也就是說回收的不及時,因此形成了必定程度上的內存泄漏

咱們再去看remove方法

image.png

哦吼!remove出克e.clean以後也調用了清除key爲null的Entry、

若是咱們的Entry裏的key用的是強引用會發生什麼,若是是強引用,就會發生更多的內存泄漏,threadLocal的引用爲null的話,由於是強引用threadlocal在垃圾回收的時候,雖然它和棧裏的引用斷了,可是他所在的Entry還被ThreadLocalMap持有呢,因此必定會發生內存泄漏

二、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對象就被五個線程共享了,也就不安全了,本來是一個線程一個房間的,可是上邊的用法致使,五個線程拿到的不是五個房間而是五個同樣的門牌號,操做的時候是在一間房子裏操做的、

三、總結

ThreadLcoal的源碼分析內存泄漏的緣由,這樣咱們使用的時候就能很大限度的避免內存泄漏,介紹了ThreadLocal不安全的一種用法!若有錯誤之處,請大佬們在評論區指出!大佬們一鍵三連!

相關文章
相關標籤/搜索