JAVA--这才是弱引用的最佳应用场景--ThreadLocal

写在前面:

文中涉及到的代码均出自jdk1.8

正文

强引用、软引用、弱引用和虚引用的概念我就不说了,网上一搜一大把,强应用和软引用的应用场景也是不少,但是这个弱引用,就没见有人讲明白过,今天,我来试试。

今天呢,我得从ThreadLocal讲起。

ThreadLocal是什么?

ThreadLocal是线程本地变量,通俗点说就是如果一个变量是ThreadLocal类型的,那么每个线程都会创建这个变量的副本,并各自维护。

这听起来有些像局部变量,是的,你可以将它理解为作用范围是全局的局部变量。(听起来真绕口,但是我觉的这样说你会懂的)

它最经典的应用场景就是数据库的Connection,它可以保证同一个线程的多个方法使用同一个Connection,从而实现跨方法的事务控制.

下面来看一个ThreadLocal的例子

我定义了一个全局变量tl,并通过两个线程去循环递增7次,我们可以看到最后的结果是14,因为tl是两个线程共有的,所以是两个线程累积递增的结果.(我们暂时先不考虑多线程造成的影响)

/**
 * @author: cuijr
 * @email: 
 * @date: 2021-01-26 19:37
 * @description:
 */
public class ThreadLocalTest {

    /**
    * 定义int变量,并初始化为0
    */
    private int tl = 0;

    /**
    * 获取变量tl,并递增至7
    */
    private void add() {
       for (int i = 0; i < 7; i++) {
            tl++;
            System.out.println(Thread.currentThread().getName() + " current number is " + tl);    
        }
    }

    public static void main(String[] args) {
       ThreadLocalTest tlt = new ThreadLocalTest();
       new Thread(() -> tlt.add()).start();
       new Thread(() -> tlt.add()).start();
    }
}

// output
Thread-0 current number is 1
Thread-0 current number is 2
Thread-0 current number is 3
Thread-0 current number is 4
Thread-0 current number is 5
Thread-0 current number is 6
Thread-0 current number is 7
Thread-1 current number is 8
Thread-1 current number is 9
Thread-1 current number is 10
Thread-1 current number is 11
Thread-1 current number is 12
Thread-1 current number is 13
Thread-1 current number is 14

那如果我们把tl变量改成ThreadLocal类型,结果会有什么不同呢?

/**
 * @author: cuijr
 * @email: 
 * @date: 2021-01-26 19:37
 * @description:
 */
public class ThreadLocalTest {

    /**
    * 定义ThreadLocal变量,并初始化为0
    */
    private ThreadLocal<Integer> tl = ThreadLocal.withInitial(() -> 0);

    /**
    * 获取变量tl,并递增至7
    */
    private void add() {
        for (int i = 0; i < 7; i++) {
            if (i == 5) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Integer a = tl.get() + 1;
            tl.set(a);
            System.out.println(Thread.currentThread().getName() + " current number is " + tl.get());
        }
    }

    public static void main(String[] args) {
        ThreadLocalTest tlt = new ThreadLocalTest();
        new Thread(() -> tlt.add()).start();
        new Thread(() -> tlt.add()).start();
    }
}

//output
Thread-0 current number is 1
Thread-0 current number is 2
Thread-0 current number is 3
Thread-0 current number is 4
Thread-0 current number is 5
Thread-1 current number is 1
Thread-1 current number is 2
Thread-1 current number is 3
Thread-1 current number is 4
Thread-1 current number is 5
Thread-0 current number is 6
Thread-1 current number is 6
Thread-1 current number is 7
Thread-0 current number is 7

我们可以清楚地看到,Thread-0和Thread-1的tl变量分别递增到了7,可以证明这两个线程各自拥有一个tl的实例。

那ThreadLocal是怎么实现在每个线程中各保存一个变量的副本呢?

普通类中,如果我们想让类的每个实例都拥有一个变量的副本,那么方法很简单,就是定义一个成员变量,(也叫全局变量)那么每个对象创建的时候都会各自保存这个成员变量的副本。

同样的,线程也可以这么干!

每个线程都会有各自的存储空间,如果我们在Thread类里定义一个成员变量,那么每个线程不就会各自保存这个成员变量的副本了吗?是的!是的!!是的!!!Thread类里有个成员变量叫threadLocals,它的类型是ThreadLocalMap,这个成员变量里存储的就是ThreadLocal,而这,也是ThreadLocal为什么能在不同的线程中拥有不同副本的根本原因。

那为什么threadLocals对ThreadLocal的引用是弱引用呢?

通过上面的实例,我们知道了我们自己的程序中会定义ThreadLocal变量,并使用强引用引用他们,当我们定义的ThreadLocal变量不再被需要时,这个变量应该会被gc回收掉,否则就会造成内存泄漏。但现在除了我们自己程序里会强应用ThreadLocal变量外,线程对象里的threadLocals也会引用这个ThreadLocal变量,如果这个引用是强引用的话,那么它就无法会被回收,直到当前线程结束。如果当前线程是在线程池里,那。。。但是,如果这个引用是弱引用呢?当我们使用完ThreadLocal变量之后,自己程序里对ThreadLocal变量的强引用就没有了,只剩下当前线程中threadLocals对它的弱引用,那么下次垃圾回收时就会把ThreadLocal变量回收掉,就不会造成内存泄漏了。

ThreadLocal里为什么可以使用弱引用?

我觉得很重要的一点是:被弱引用的对象必须要被一个无法轻易操作的类引用。

怎么理解这句话呢?

还是以ThreadLocal为例,ThreadLocal对象本身必须被当前线程持有,这个当前线程时我们无法轻易操作的。我们用普通的强引用使用ThreadLocal变量,当不需要这个变量时,这个强引用就没了,只剩下个弱引用,那这个ThreadLocal变量自然就可以被回收了。

补充:

当然,当前线程对我们来说并不是不可操作的,毕竟我们可以通过Thread.currentThread().threadLocals来获取这个map,然后通过remove来移除不用的对象。

但是,如果每用完一个ThreadLocal变量,就需要调用Thread.currentThread().threadLocals去删除它,那么这个ThreadLocal也太难用了,这能是Doug lea这等大神容许的?所以在这里,弱引用是个极好的选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值