ThreadLocal实现线程绑定

后端 潘老师 5个月前 (12-18) 105 ℃ (0) 扫码查看

本文主要讲解关于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();
    }
}

以上就是关于ThreadLocal实现线程绑定相关的全部内容,希望对你有帮助。欢迎继续关注潘子夜个人博客,学习愉快哦!


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/back/12610.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】