JUC学习:ThreadLocal思想及源码分析

1 ThreadLocal介绍

        ThreadLocal提供了线程的局部变量。每个线程都有一份自己的、独立初始化的变量。这样的设计就能保证线程的安全(多线程修改共享变量不安全,修改自己的变量安全)。

2 ThreadLocal案例

        现有需求如下:销售员销售房子,年终需要统计每个销售员分别销售了多少套房子。

        该案例中每个销售员就相当于是一个线程,销售的房子数即为每个线程的局部变量,每个线程修改其自身的变量(即销售房子数)。

        注意:在使用完ThreadLocal变量时,需要手动Remove,否在在多线程使用线程池的情况下,由于线程的复用,可能会导致内存泄露的问题。

class SalesMan {

    //创建ThreadLocal线程局部变量,并赋初始值0
    ThreadLocal<Integer> salesCount = ThreadLocal.withInitial(() -> 0);

    //每调用一次数量加1
    public void saleHouse() {
        salesCount.set(salesCount.get() + 1);
    }
}

public class ThreadLocalDemo1 {

    public static void main(String[] args) {

        SalesMan salesMan = new SalesMan();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                int random = new Random().nextInt(5) + 1;
                try {
                    for (int j = 0; j < random; j++) {
                        salesMan.saleHouse();
                    }
                } finally {
                    salesMan.salesCount.remove();
                }
                System.out.println(Thread.currentThread().getName() + " 号销售卖出 " + salesMan.salesCount.get());
            }, String.valueOf(i)).start();
        }
    }
}

//输出
2 号销售卖出 5
3 号销售卖出 1
4 号销售卖出 2
0 号销售卖出 4
1 号销售卖出 5

2 ThreadLocal源码分析

        前置知识,Thread、ThreadLocal与ThreadLocalMap。

        一个线程是一个Thread;线程的一个局部变量就是一个ThreadLocal;一个线程可以有多个局部变量(ThreadLocal),这多个局部变量就存在ThreadLocalMap(每个线程独有一份)里。

        在JUC源码中Thread类中引用了一个ThreadLocalMap,里面存储了这个线程的所有ThreadLocal字段。

         从上图可以看出,ThreadLocalMap类通过ThreadLocal类引用,它属于ThreadLcoal类的一个静态内部类,其中map的key是ThreadLocal(线程局部变量字段),value对应的是该ThreadLoval的值。

2.1 get()方法源码分析

        这是get()方法的源码。

         上图可以看出,在调用ThreadLocal的get()方法时需要先获取到当前线程,并同通过getMap()方法获取当前线程的ThreadLoaclMap(里面存储了该线程的所有局部变量)。以下是getMap()方法的源码,可以看出只是通过线程(t)调用了其自身的ThreadLocalMap。

        然后判断是否初始化过该ThreadLocalMap,未初始化则调用setInitialValue()方法初始化;若已初始化过,则通过map.getEntry(当前ThreadLocal)获取Map中key对应ThreadLocal的Entry,并返回Entry的value值。如下是setInitialValue()方法的源码。其中将ThreadLocal字段的值初始化为null,再获取当前线程的ThreadLocalMap,若map已经创建过则直接set对应ThreadLocal的值,若不存在,则调用createMap()方法为当前线程创建一个ThreadLocalMap,并进行赋值操作。

总结:

  1. 调用ThreadLocal的set()方法时,实际上就是向ThreadLocalMap中设置值,key是ThreadLocal对象,value是set传递进来的值。
  2. 调用ThreadLocal的get()方法时,实际上就是通过ThreadLocal作为key,从ThreadLocalMap中获取value;

ThreadLocal本身并不存储数据,它只是作为一个key让线程从ThreadLocalMap中获取value。

3 ThreadLocal的内存泄露问题

        前置知识。弱引用,弱引用的对象在jvm发生垃圾回收时,若jvm对空间还充足,则不会回收该对象;在jvm内存空间不足时,会回收弱引用对象。以下是内存泄露的两种情况以及处理方案。

  1. 当一个方法执行完毕ThreadLocal需要被回收,但此时ThreadLocalMap中的key还指向这个对象,则会出现ThreadLocal无法被回收的情况,出现内存泄露。解决方法:将ThreadLocalMap中的key(ThreadLocal)设置为弱引用,gc内存不够时直接回收。
  2. 脏Entry。当ThreadLocal对象被回收,导致Entry中的key为null,但是value中的对象还存在,并且无法被访问回收,从而导致内存泄露。解决方法:ThreadLocal类在进行get、set等方法时,会将key为null的Entry的value直接置为null,从而防止了内存泄露。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值