欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ThreadLocal底层原理实现

程序员文章站 2022-07-03 15:21:18
...

概述

ThreadLocal是java.lang包中的一个类,用来实现变量的线程封闭性,即只有当前线程可以操作该变量,通过把一个变量存在当前线程的一个Map容器中来实现。当然,这样解释很抽象,对于一些人来说难以理解,这里我先介绍一个《java并发编程实战》中描述的jdbc应用场景,来让你知道ThreadLocal到底有什么用。

ThreadLocal的意义

一般来讲,在单线程应用程序中可能会维持一个全局的数据库连接,并在程序启动时初始化这个连接对象,从而避免在调用每个方法(save,get等)时都要传递一个Connection对象。由于Jdbc连接对象不一定是线程安全的,因此,当多线程应用程序在没有协同的情况下使用全局变量时,就不是线程安全的,比如线程一刚获取全局的Connection,准备进行数据库操作,但线程二却执行了Connection.close()。

这种情况下我们可能会取消Connection这个全局变量,在每次要进行数据库相关操作时直接new一个Connection对象进行连接,而这又会导致一个线程执行多次数据库操作时要new多个connection对象,加大系统的负担。这时就会想能不能有这样一种方法,既不让Connection成为全局变量来保证线程安全,又可以实现全局Connection带来的“一次连接”式的便利?ThreadLocal就是做这件事的。

简要应用

下面第一行代码new了一个静态的ThreadLocal<Connection>。以后只要让每个线程在进行jdbc操作前都执行第二行代码,就会把一个创建的Connection对象存放到当前线程的map容器中(保留个引用),后面该线程要进行数据库操作时只要执行第三行代码,就能拿到这个引用进行连接,不用每次都new一个新的。

你可能会问只是一句简单的connectionHolder.get()代码,而且ThreadLocal对象只有一个,那怎么能准确的拿到当前线程中的Connection呢?答案就是这个connection是存在当前线程(一个Thread)中的,不是ThreadLocal中。表面上调用的ThreadLocal.get()和get(),实际上是在当前线程对象中进行设置和查找。不过当前线程的map容器中可能会存多个ThreadLocal的值,所以Map中的key就是ThreadLoca对象,值就是connection,来进行区分。还不懂的话见下面的代码解析,很简单。

private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();

connectionHolder.set(DriverManager.getConnection(DB_URL));

connectionHolder.get();

源代码分析

Thread的map容器

threadLocals是Thread类的一个属性,该字段的类型是ThreadLocalMap。

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

而ThreadLocalMap是ThreadLocal类中的静态内部类,类字段如下:

private static final int INITIAL_CAPACITY = 16;

private int size = 0;

private int threshold;

private Entry[] table;    //Thread的map容器

static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

大家可能会好奇,table中的Entry只有一个value,并没有像Hashmap中那样是键值对啊,为什么叫它map容器呢?其实Entry是有key的,不过该字段是从Reference类中继承的,从Entry的构造方法中可看到传入了一个ThreadLocal对象,它就是key,我们追踪super(k),最后发现在Entry的父类Reference中:

private T referent;       
  
//ThreadLocal最终传给了下面方法的第一个参数
Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }

知道了Thread的map容器的结构,那接下来看看ThreadLocal的其他方法。

ThreadLocal的set方法

先拿到当前线程的map容器,再进行插入或new一个新的。

public void set(T value) {
        Thread t = Thread.currentThread();    //获取当前线程对象
        ThreadLocalMap map = getMap(t);       //拿到t的map容器
        if (map != null)        
            map.set(this, value);            //map不空,根据key(调用set的ThreadLocal)设置值
        else
            createMap(t, value);             //map为空则创建一个map(第一次插入,有点怪怪的)
    }

ThreadLocalMap的set方法

类似于HashMap的put,但没有对链表的遍历,是对table的遍历。这里我没有对ThreadLocalMap中的replaceStaleEntry()、cleanSomeSlots()进行仔细研究,以后有时间了再介绍吧。

private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;

            //根据ThreadLocal的hash值计算应该从table中哪个位置开始(跟HashMap一样)
            int i = key.threadLocalHashCode & (len-1);

            //遍历table中的每一个Entry
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                //如果找到相同的key,覆盖并返回
                if (k == key) {
                    e.value = value;
                    return;
                }

                //如果发现了空key为空,即ThreadLocal对象被GC了,就把数据放到该位置
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            //当返现e==null时,执行下面代码,new再检测是否扩容
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

ThreadLocal的get方法

很简单,如果map里面已经存了对应ThreadLocal的值,则返回;否则执行setInitialValue方法。

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

ThreadLocal的setInitialValue方法

会返回一个初始值,由initialValue返回。可以看到initialValue方法只是返回了一个null,如果我们不重写它的话。同时再加一个set中的判断,若map为null,把initialValue返回的初值也存进去;否则创建一个map再存。

private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

protected T initialValue() {
        return null;
    }

如果大家想要对ThreadLocal有更直观的了解,可以看一下这篇文章:https://mp.weixin.qq.com/s/aM03vvSpDpvwOdaJ8u3Zgw