ThreadLocal 实现线程绑定

ThreadLocal实现线程绑定

​ 为了实现数据库事务管理,多条sql请求应当使用同一个Connection,然而事务控制包含处理逻辑,应属于服务层,而SQL请求属于dao层,于是乎,就有了这样一条需求:在不同的类中都可以拿到同一个对象实例,但不同的线程取到的实例不同我们将这种数据称之为线程本地变量,这种操作叫做线程绑定

​ 可见需求分为两部分,一、不通过传参实现在不同的类中获得相同的实例,二、实例与线程对应,同一个线程获取到的是同一个实例,不同线程获取到的却不能相同。

怎么实现“不通过传参,在任意位置获取同一个实例”

​ 对于第一点很好保证,通过将变量赋值给类的静态成员,这样在其他类中就可以通过类名获取到数据。我们还可以将这个类抽取为工具类,如下:

public class BindUtils {
	private static MyData myData;

    public static MyData getMyData(){
        if(myData==null){
            synchronized(BindUtils.class) {
                if(myData==null) {
                    myData = new MyData();
                }
            }
        }
        return myData;
    }
}

// 这样就可以在其他类中,任意位置获取到MyData这个变量了。
class OtherClass1{
    void xxxfunc(){
        BindUtils.getMyData();
    }
} 

但是显然MyData属于类的静态成员,在堆内存中,被所有的线程共享,不同的线程拿到的myData肯定是同一个。

怎么实现“不同的线程获取到不同的实例”

在前者的基础上使用ThreadLocal改造即可:

public class BindUtils {
    private static ThreadLocal<MyData> tl = new ThreadLocal<>();

    public static MyData getMyData() {
        if (tl.get() == null) {
           tl.set(new MyData());
        }
        return tl.get();
    }
}

ThreadLocal是如何做到线程绑定的呢?

​ 每个线程都有一个Thread实例,Thread类中有一个成员变量threadLocals,该变量类似于一个Map集合。其中可以保存键值对。由于它是成员变量,所以每个Thread实例互不相同,二每个Thread实例对应各自的线程,所以将数据放到threadLocals里,就实现了与线程绑定。

​ 然而threadLocals并不是真正的map集合,我们也无法直接操作。所以把数据放进去和取出来都需要通过ThreadLocal操作。调用ThreadLocal的set方法,可以将数据放入,放入时以ThreadLocal实例【此处为 tl】为键,以要存入的数据为值。get取出数据时也是以自身为键,取出对应的值。

​ 打个比方就是:每个线程都有一个仓库threadLocals,ThreadLocal的实例就像是一个令牌,拿着令牌就可以把一样东西存入仓库,同样还是呀拿着这个令牌就能把货物从仓库取出。由于每个线程一个仓库,所以不同的线程拿着相同的令牌取出的东西也不同。

​ 所以现在只需要将这个令牌通过类的静态成员【tl】暴露给所有的类,那么就可以在任意类中通过这个令牌到各自线程的仓库中取出各自的数据。如此便实现了数据的线程绑定。

其他

一、这里使用的是静态成员实现所有类都可以访问,在Spring中容器中的bean也是唯一的,可以注入到任意类中的;所以也可以将此工具类作为一个bean放到容器中,实现相同的效果。

二、ThreadLocal除了get set 还有两个方法,也比较重要:

protected T initialValue() 可见它是一个protected方法,那用法自然是重写此方法:

private static ThreadLocal<MyData> tl = new ThreadLocal<>(){
    @Override
    protected MyData initialValue() {
        return new MyData();
    }
};

作用l:在第一次调用get时,如果如果得到的值还是null,就会调用此方法,将返回值进行set并返回。也就是初始化的作用。

ThreadLocal.withInitial(()->{return xxx;}); 作用效果与上例继承重写initialValue()一样,只不过他是一个静态方法,用起来更顺手,还可以使用函数引用优化。如下:

@Component
public class BindUtils {
    private ThreadLocal<MyData> tl = ThreadLocal.withInitial(MyData::new);

    public MyData getMyData() {
        return tl.get();
    }
}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
ThreadLocal是Java中的一个线程本地变量工具类,用于在多线程环境下,为每个线程提供独立的变量副本。它的实现原理是通过在每个线程中维护一个ThreadLocalMap对象,该对象以ThreadLocal实例作为key,以对应的值作为value,存储线程的局部变量。 ThreadLocal的应用场景有: 1. 线程安全的对象:如果一个对象在多线程环境中被共享访问,并且它的状态是可变的,那么使用ThreadLocal可以为每个线程提供一个独立的对象副本,避免线程间的竞争和同步问题。 2. 数据库连接管理:在使用数据库连接池时,每个线程需要从连接池中获取数据库连接进行操作,ThreadLocal可以用来存储当前线程所使用的数据库连接,保证每个线程都有自己独立的连接。 3. 事务管理:在多线程环境下,如果某个业务操作需要开启事务,并且事务的隔离级别是线程级别的,则可以使用ThreadLocal来存储当前线程事务状态,确保每个线程操作的是自己的事务。 4. Web应用中的用户信息存储:在Web应用中,每个请求都会创建一个线程进行处理,可以使用ThreadLocal将用户信息保存在当前线程中,方便在不同的业务逻辑中获取用户信息。 5. 并发工具类中的上下文传递:在一些并发框架和工具类中,可能会需要将一些上下文信息传递给线程池中的线程,使用ThreadLocal可以将上下文信息绑定当前线程,方便在线程池中进行访问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值