资讯专栏INFORMATION COLUMN

Java多线程基础-ThreadLocal

JasonZhang / 2845人阅读

摘要:并没有提供语言级的线程局部变量,而是在类库里提供了线程局部变量的功能,也就是这次的主角类。

Yuicon 转载请注明原创出处,谢谢!

在多线程环境下,访问非线程安全的变量时必须进行线程同步,例如使用synchronized方式访问HashMap实例。但是同步访问会降低并发性,影响系统性能。这时候就可以用空间换时间,如果我们给每个线程都分配一个独立的变量,就可以用非同步的方式使用非线程安全的变量,我们称这种变量为线程局部变量。

顾名思义,线程局部变量是指每个线程都有一份属于自己独立的变量副本,不会像普通局部变量一样可以被其他线程访问到。Java并没有提供语言级的线程局部变量,而是在类库里提供了线程局部变量的功能,也就是这次的主角ThreadLocal类。

ThreadLocal的使用

Java8版本的ThreadLocal有上图所示的4个public方法和一个protected的方法,第一个方法用于返回初始值,默认是null。第二个静态方法withInitial(Supplier supplier)Java8版本新添加的,后面三个实例方法则非常的简单。

Java8之前,使用ThreadLocal时想要设置初始值时需要继承ThreadLocal类覆盖protected T initialValue()方法才行,例如:

ThreadLocal threadLocal = new ThreadLocal() {
    @Override
    protected Integer initialValue() {
        return 0;
    }
};

在Java8版本可以使用新添加的静态方法withInitial(Supplier supplier),非常方便的设置初始值,例如:

ThreadLocal threadLocal = ThreadLocal.withInitial(() -> 0);

System.out.println(threadLocal.get());
threadLocal.set(16);
System.out.println(threadLocal.get());
threadLocal.remove();
System.out.println(threadLocal.get());

// 同一个线程的输出
0
16
0

Process finished with exit code 0
ThreadLocal的原理

那么ThreadLocal是怎么实现线程局部变量的功能的呢?其实ThreadLocal的基本原理并没有十分复杂。ThreadLocal在内部定义了一个静态类ThreadLocalMapThreadLocalMap的键为ThreadLocal对象,ThreadLocalMap的值就是ThreadLocal存储的值,不过这个ThreadLocalMap是在Thread类里维护的。我们来看一下ThreadLocal的部分源码:

    // ThreadLocal的set方法
    public void set(T value) {
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取Map
        ThreadLocalMap map = getMap(t);
        if (map != null)
            // 设置值
            map.set(this, value);
        else
            // 初始化Map
            createMap(t, value);
    }
    
    // ThreadLocal的createMap方法
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    
    // Thread类定义的实例域
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

可以看出ThreadLocal的核心实现就是ThreadLocalMap的实现了,ThreadLocalMap内部声明了一个Entry类来存储数据:

static class Entry extends WeakReference> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal k, Object v) {
        super(k);
        value = v;
    }
}

ThreadLocalMap的实现与HashMap的实现有相似的地方,比如同样是使用数组存储数据和自动扩容,不同的是hash算法与hash碰撞后的处理不一样。

        // ThreadLocalMap的set方法
        private void set(ThreadLocal key, Object value) {

            Entry[] tab = table;
            int len = tab.length;
            // 计算在Entry[]中的索引,每个ThreadLocal对象都有一个hash值threadLocalHashCode,每初始化一个ThreadLocal对象,hash值就增加一个固定的大小0x61c88647
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal k = e.get();
                // 如果键已存在就更新值
                if (k == key) {
                    e.value = value;
                    return;
                }
                // 代替无效的键
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
        
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

可以看到ThreadLocalMap把Entry[]数组当成一个圆环。从计算出来的索引位置开始,如果该索引已经有数据了就判断Key是否相同,相同就更新值。否则就直到找到一个空的位置把值放进去。获取值的时候也类似,从计算出来的索引位置开始一个一个检查Key是否相同,这样hash碰撞比较多的话可能性能就不是很好。

ThreadLocal的应用

ThreadLocal的应用是非常广的,比如Java工程师非常熟悉的Spring框架中就使用了ThreadLocal来把非线程安全的状态性对象封装起来,所以我们可以把绝大部分的Bean声明为singleton作用域。我们在编写多线程代码时也可以想想是用同步的方式访问非线程安全的状态性对象比较好,还是使用ThreadLocal把非线程安全的状态性对象封装起来更好。

后记

本来下定决心准备一周一篇的,结果偷懒了一次后赶上了公司旅游。这一下子摸了两篇,只能后面慢慢补了……ThreadLocal我很早就看到过了,一直没什么实感,直到在《精通Spring 4.X 企业应用开发实战》看到在Spring中的应用后才发现,我从来没想过为什么Spring里的Dao类可以声明为单例作用域……没有举一反三的能力就只能多看书了,活到老学到老。

参考资料:

《Java核心技术 卷一》

《精通Spring 4.X 企业应用开发实战》

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/71698.html

相关文章

  • Java基础进阶之ThreadLocal详解

    摘要:基本在项目开发中基本不会用到但是面试官是比较喜欢问这类问题的所以还是有必要了解一下该类的功能与原理的是什么是一个将在多线程中为每一个线程创建单独的变量副本的类当使用来维护变量时会为每个线程创建单独的变量副本避免因多线程操作共享变量而导致的数 ThreadLocal基本在项目开发中基本不会用到, 但是面试官是比较喜欢问这类问题的;所以还是有必要了解一下该类的功能与原理的. Thread...

    worldligang 评论0 收藏0
  • Java线程基础(十三)——Thread-Specific Storage(ThreadLocal

    摘要:案例中的类就是线程独有对象的代理者参与者参与者会处理多个委托的工作。然而,的实现思路让每个对象,自身持有一个,这个的就是当前对象,是本地线程变量值。 一、定义 Thread-Specific Storage就是线程独有的存储库,该模式会对每个线程提供独有的内存空间。java.lang.ThreadLocal类提供了该模式的实现,ThreadLocal的实例是一种集合(collecti...

    warnerwu 评论0 收藏0
  • (四)java线程之同步基础ThreadLocal

    摘要:本人邮箱欢迎转载转载请注明网址代码已经全部托管有需要的同学自行下载引言之前我们讲到都是多线程共享数据那么有没有某一个共享的变量在这变量里面每个线程都能拥有自己的属性呢比如说去旅店开房休息那么这个旅店就是一个共享的数据但是每个人开的房间是不一 本人邮箱: 欢迎转载,转载请注明网址 http://blog.csdn.net/tianshi_kcogithub: https://github...

    Lucky_Boy 评论0 收藏0
  • Java面试题必备知识之ThreadLocal

    摘要:方法,删除当前线程绑定的这个副本数字,这个值是的值,普通的是使用链表来处理冲突的,但是是使用线性探测法来处理冲突的,就是每次增加的步长,根据参考资料所说,选择这个数字是为了让冲突概率最小。 showImg(https://segmentfault.com/img/remote/1460000019828633); 老套路,先列举下关于ThreadLocal常见的疑问,希望可以通过这篇学...

    Maxiye 评论0 收藏0
  • ThreadLocal基本原理及运用

    摘要:基本原理线程本地变量是和线程相关的变量,一个线程则一份数据。其中为声明的对象。对于一个对象倘若没有成员变量,单例非常简单,不用去担心多线程同时对成员变量修改而产生的线程安全问题。并且还不能使用单例模式,因为是不能多线程访问的。 ThreadLocal简述 下面我们看一下ThreadLocal类的官方注释。 This class provides thread-local variab...

    VEIGHTZ 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<