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不安全的一种用法!若有错误之处,请大佬们在评论区指出!大佬们一键三连!

相关文章
相关标签/搜索