ThreadLocal原理(一)

ThreadLocal的基本使用

ThreadLcoal位于JDK的java.lang核心包中.如果程序创建了一个ThreadLocal实例,那么在访问这个变量的值的时候,每个线程都会有一个独立的 自己的本地值.再多线程并发操作本地变量的时候,线程各自操作自己本地的值,从而避免线程安全问题.

方法说明
set(T value)设置当前线程在"线程本地变量"实例中绑定的值
T get()获取当前线程在"线程本地变量"实例中绑定的值
remove()移除当前线程在"线程本地变量"实例中绑定的值
public class ThreadLocalDemo1 {

    @Data
    static class Local {
        //实例总数.
        static final AtomicInteger AMOUNT = new AtomicInteger(0);
        //对象的编号.
        int index = 0;
        //对象的内容.
        int bar = 10;

        //构造器.
        public Local() {
            index = AMOUNT.incrementAndGet();
        }

        @Override
        public String toString() {
            return index + "@Local{bar" + bar + "}";
        }
    }

    private static final ThreadLocal<Local> Local = new ThreadLocal<>();

    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    if (Local.get() == null) {
                        Local.set(new Local());
                    }
                    System.out.println("线程初始本地值是" + Local.get());
                    for (int i1 = 0; i1 < 10; i1++) {
                        Local local = Local.get();
                        local.setBar(local.getBar() + 1);
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("累加十次线程本地值是" + Local.get());
                    Local.remove();
                }
            });
        }
        threadPool.shutdown();
    }
}

 通过运行这个例子可以看出,在多线程下,threadLocal的结果并没有出现线程不安全的现象.ThreadLocal还有一个静态工厂方法

public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }

在定义ThreadLocal对象时设置一个初始的回调函数.

ThreadLocal<Local> localThreadLocal = 
ThreadLocal.withInitial(() -> new Local());

 这种用法通过调用工厂方法创建了一个ThreadLocal对象,并传递了一个获取初始值的Lambda回调函数.我建议大家可以去看看这个方法的实现.相信这个彩蛋会让你有意想不到的发现.

ThreadLcoal使用场景

1:线程隔离

ThreadLcoal的主要价值在于线程隔离,其中的数据只属于当前线程,本地值对其他线程是不可见的,多线程环境下,可以防止自己的变量被其他线程篡改.由于各个线程之间的数据隔离,避免了同步加锁带来的性能损失,提升了并发的性能.

线程隔离使用案例

每个线程绑定一个用户会话信息 数据库连接 http请求等.这样一个线程所有调用到的处理函数都可以很方便的访问这些资源.

使用场景

数据库连接独享 Session数据管理等.

2:跨函数传递数据

通常用于同一个线程内,跨类 跨方法传递数据时,如果不用ThreadLcoal,那么相互之间传递数据势必要靠返回值和参数,无形之间增加了类或方法之间的耦合度.

由于ThreadLocal的特性,同一线程在某些地方进行设置,再随后的任意地方都可以获取.线程执行过程中所涉及到的函数都能读写线程本地值,从而方便的实现跨函数的数据传递.

使用场景

可以为每一个线程绑定一个Session(用户会话)信息,一个线程所访问的地方可以很方便的访问这个本地会话.

ThreadLocal内部结构演进

在早期的JDK版本中ThreadLocal内部结构是一个Map,其中线程实例为Key,线程绑定的线程本地变量为Value.这个Map的拥有者为ThreadLocal实例.每一个实例拥有一个Map实例.

大部分应用中,线程可能会配置几百个,本地变量也就配置几个.

了解过HashMap的话,会知道在扩容的时候存在高成本低性能的问题.因为HashMap内部是一个槽位(slot)数组,这个数组也叫哈希表,存储的是key的哈希值,当槽位数组中的元素个数超过默认容量(16)乘以加载因子(0.75)的时候会进行扩容,扩为容量为32的数组.对于每一个槽位,可以理解为桶(bucket),如果一个桶内元素超过8个,链表会变成红黑树,这是都是高性能低成本的工作.

ThreadLocal实例内部的Map结构叫做ThreadLcoalMap,没有直接采用HashMap对象,而是自定义的和HashMap类似的结构,与HashMap不同之处在于ThreadLcoalMap去掉了桶结构,如果发生哈希碰撞,将key相同的Entry放在槽位后面相邻的空闲位置.HashMap(数组加链表)的处理方式叫做链地址法,发生碰撞就把Entry放在链表中.ThreadLcoalMap的叫做开放地址法,即发生碰撞,就按照某种方法继续寻找其他存储单元,直到找到空位置.

ThreadLocalMap和HashMap一样存在扩容的问题,在线程比较多局部变量比较少的场景下是不是可以转换思路,将ThreadLcoal实例变成Key,一个线程一个Map,这样可以避免扩容的性能消耗.

在JDK8中ThreadLcoal内部结构发生了变化,还是使用Map结构,但是拥有者发生了变化,拥有者为Thread实例,每个Thread实例拥有一个Map实例,Map结构的key也发生了变化,变成了ThreadLcoal实例.

然后每一个Thread线程内部都有一个Map(ThreadLcoalMap),如果我们给一个线程创建了多个ThreadLocal实例,然后放置本地数据,线程的ThreadLocalMap中就会有多个key-value,ThreadLocal为key,本地变量为Value.

早期版本与新版本变化

1:拥有者发生变化:新版本的ThreadLocalMap拥有者为Thread,早期版本为ThreadLocal.

2:key发生了变化:新版本的key为ThreadLcoal实例,早期为Thread实例.

新版本的优势

1:每个ThreadLocalMap存储的key-value数量变少.早期版本的key-value数量和线程强关联,若线程数量多,ThreadLcoalMap存储的key-value数量也多.新版本的key为ThreadLocal实例,多线程情况下,ThreadLcoal实例少于线程数.

2:早期版本ThreadLcoalMap的拥有者为ThreadLocal,在Thread线程被销毁后,ThreadLocalMap还是存在的,新版本的拥有者为Thread,当Thread实例被销毁后,ThreadLocalMap也会随之销毁,减少内存的消耗.

会迷茫,会抑郁,但是好在我还是在追逐这诗与远方.

如果大家喜欢我的分享的话,可以关注一下微信公众号

心有九月星辰

  • 20
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值