ThreadLocal浅析

ThreadLocal原理和思想浅析

基本思想、原理、应用

首先我们知道,如果多个线程共同操作同一块 共享内存 或者说 共享变量 的时候,那么很有可能发生错误。最典型的就是

public class MyAtomicInteger {
    private int value;
    
    public void increaseAndGet() {
        value += 1;
        return value;
    }
}

那么比较典型的思想有:

  1. 用时间换安全

  2. 用空间换安全

    每个线程单独有需要操作的对象。

第一种方式是通用的。

第二种方式有时候可以单独使用,有时候需要配合使用。

锁就不举例子了。

用空间换安全就是说把每个变量复制一份放到各个线程里,线程当然操作的就是自己的变量,不再是共享变量了

因为多线程发生问题的地方就是 操作了共享变量

所以我们可以使用ThreadLocal应用在相同初始配置的变量但是会被多线程并发的情况,一个线程无论怎样操作自己的变量都不会有问题。

例如:

  1. 使用SimpleDateFormat,因为其内部是非线程安全的,没有使用锁来控制,所以就会出现问题。如果我们想要对于每个线程按照相同的格式化标准,比如

    public class Test {
        private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
        public void test() {
            // 假设有多个线程,出问题
            simpleDateFormat.parse("");
            // 
        }
        
        private static final ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>(){
            // 只要当前线程没有该SimpleDateFormat变量,就会创建,否则就会取出当前线程对应的SimpleDateFormat对象。
            // 对于每一个线程都创建一个SimpleDateFormat对象。
            @Override
            public SimpleDateFormat initialValue() {
                return new SimpleDateFormat("yyyyMMdd");
            }
        };
        
        public void threadLocal() {
            // 假设有多个线程,每个线程操作的是自己的SimpleDateFormat对象
            threadLocal.get().parse();
        }
    }
    

    其实你也可以每次方法创建一个对象,不过有点浪费,因为方法结束该对象就会被销毁

    public void threadLocal() {
    	// 假设有多个线程,每个线程操作该方法都会使用新建的SimpleDateFormat对象,不会发生错误问题
    	new SimpleDateFormat("yyyyMMdd").parse();
    }
    
  2. Connection对象的创建

    由于想要保证对象初始配置相同,且在整个线程运行期间不会变,而且当前线程connection改变不会影响别的。

    一个线程一个Connection。

    private static final ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>(){
            @Override
            public Connection initialValue() {
                // ...
                return new Connection();
            }
        };
    

源码思路

关于get和set其实比较简单,就是获取当前线程的ThreadLocalMap对象,然后如果是第一次get,就调用initialValue方法初始化即可。

网上文章很多了。

重点在于为什么ThreadLocalMapkey是虚引用。

这得从内存泄露说。

是说如果我们在使用完ThreadLocal对象存储的值后,如果我们不再使用值了,只是进行了如下操作。

private static final ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>(){
    // 只要当前线程没有该SimpleDateFormat变量,就会创建,否则就会取出当前线程对应的SimpleDateFormat对象。
    // 对于每一个线程都创建一个SimpleDateFormat对象。
    @Override
    public SimpleDateFormat initialValue() {
        return new SimpleDateFormat("yyyyMMdd");
    }
};

threadLocal = null;

只是把threadLocal的指向置为空,但是由于ThreadLocalMap被当前线程引用,所以当前的threadLocal还是可以在当前线程存活的情况下被找到,对应存储的Entry对象也会被找到。

在这里插入图片描述

弱引用的情况:
在这里插入图片描述

在这里插入图片描述

既然是这样,那么强引用和弱引用解决的方案都是删除Entry就可以了,那么为什么还需要弱引用呢?

还是怕我们忘记删除Entry对象

所以JDK是这样操作的:

改成弱引用就能保证ThreadLocal不被强引用时,ThreadLocal会帮我们去清理掉key为null的value,这样就避免内存泄露了。

所以什么时候会去清理呢。

就是set/getEntry时候。

因为解决ThreadLocalMap冲突的方式是线性探测再散列,所以会把遍历到的都检测一遍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值