ThreadLocal 简述

ThreadLocal 简述

前言

之前面试又被问过关于 ThreadLocal 的使用以及实现原理,回答的不是很好,在这里会对 ThreadLocal进行分析,介绍,以及总结性概述

场景

最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。如

class ConnectionManager {
     
    private static Connection connect = null;
     
    public static Connection openConnection() {
        if(connect == null){
            connect = DriverManager.getConnection();
        }
        return connect;
    }
     
    public static void closeConnection() {
        if(connect!=null)
            connect.close();
    }
}

上面是我们最初的数据库连接的一个设计,上面主要有三个问题:

  • 线程同步问题,这种做法显然是不符合并发的,会多次生产 connect 对象
  • 会有一个线程在使用关闭的方法,另一个线程还在使用这个连接
  • 一个线程在使用连接对象的时候,其他的对象只能够等待,影响执行效率

针对上面的两个问题,在下面进行了改进,使用了今天的主角ThreadLocal,为每个线程都创建一个私有的连接对象

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


public static Connection getConnection() throws InfrastructureException{
  Connection connection;
  try {
        if ((connection = connectionHolder.get()) == null) {
     			connection = DriverManager.getConnection(DB_URL);
          connectionHolder.set(connection);
   			}
    } catch (HibernateException ex) {
        throw new InfrastructureException(ex);
    } 

    return connection;
}

public static void closeConnection() {
  Connection connection;
  if((connection = connectionHolder.get()) != null)
       connection.close();
}

实现原理

以上是我们使用ThreadLocal实现数据库连接的一种场景,接下来我们先看下ThreadLocal的内部实现原理

ThreadLocal

先看一下set()方法

set
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
}

//创建一个ThreadLocalMap实例,并将实例的引用保存到当前线程的属性中
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
}

以上就是set方法的全部内容

  • 首先就是获取到当前的线程t
  • 获取当前线程 tThreadLocalMap
  • 判断 ThreadLocalMap是否为空,返回存放了value值的ThreadLocalMap

我们继续看下get()方法

get()
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();
    }

以上就是get()方法的内容,我们简单看下

  • 获取当前线程的ThreadLocalMap;为null,初始化ThreadLocalMap后返回;
  • 不为空,将ThreadLocal对象作为key,从 map 中获取到value

ThreadLocal$ ThreadLocalMap

从上面ThreadLocalget()set方法中,我们看到存储的容器是ThreadLocalMap

我们接下来看下关于ThreadLocalMap的代码

//ThreadLocalMap 是 ThreadLocal 的一个内部类
static class ThreadLocalMap {

  			//内部的 Entry 则是用来保存我们需要保存的变量容器
  			//这里可以看到 Entry 继承了WeakReference,详见页尾附录
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;

          	//key 就是当前 ThreadLocal 的对象
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

  			//firstKey就是当前 ThreadLocal的对象引用,
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

        private void set(ThreadLocal<?> key, Object value) {
					 ...
        }

        private void remove(ThreadLocal<?> key) {
           ....
        }
			...
    }

以上是 JDK 中的源代码部分,我们用一张图来表示一下

Thread

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

每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。

关系引用图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YOS3GBKv-1591232063363)(/Users/toner/Library/Application Support/typora-user-images/image-20200603185652718.png)]

总结

  • Thread实例会维护一个ThreadLocalMap的引用
  • ThreadLocalMapThreadLocal的内部类,在其内部有一个Entry来进行存储变量
  • ThreadLocal#set()中,我们将当前ThreadLocal的引用作为 key,传入的 obj 作为 value,保存进ThreadLocalMap$Entry
  • ThreadLocal本身不存储值,仅仅作为一个key来让线程从ThreadLocalMap获取value

附录

为何Entry使用弱引用?

在上面ThreadLocalMap中,我们看到Entry是继承了WeakReference,那么问题来了,为什么要继承?不继承能不能实现现有的功能。

未完待续。。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值