面试大厂肯定离不开的——ThreadLocal,它的实现原理你知道吗?

作者 : 开心源码 本文共3678个字,预计阅读时间需要10分钟 发布时间: 2022-05-13 共140人阅读
推荐阅读:阿里二面凉经:虚拟机+MySQL+中间件+设计模式+缓存+Spring+并发等难题,一律迎刃而解

使用场景

假设我们有一个数据库连接管理类:

class ConnectionManager {    private static Connection connect = null;    private static String url = System.getProperty("URL");    public static Connection openConnection() {        if(connect == null){            try {                connect = DriverManager.getConnection(url);            } catch (SQLException e) {                e.printStackTrace();            }        }        return connect;    }    public static void closeConnection() {        if(connect!=null) {            try {                connect.close();            } catch (SQLException e) {                e.printStackTrace();            }        }    }}

假如这个类被用在多线程环境内,则会存在线程安全问题,那么可以对这两个方法增加synchronized关键字进行同步解决,不过这样会大大降低程序的性能,也可以将connection变成局部变量:

class ConnectionManager {    private Connection connect = null;    public Connection openConnection(String url) {        if(connect == null){            try {                connect = DriverManager.getConnection(url);            } catch (SQLException e) {                e.printStackTrace();            }        }        return connect;    }    public void closeConnection() {        if(connect!=null) {            try {                connect.close();            } catch (SQLException e) {                e.printStackTrace();            }        }    }}class ConnectionManagerTest {    private String url = System.getProperty("URL");    public void insert() {        ConnectionManager connectionManager = new ConnectionManager();        Connection connection = connectionManager.openConnection(this.url);        //使用connection进行操作        connectionManager.closeConnection();    }    public void update() {        ConnectionManager connectionManager = new ConnectionManager();        Connection connection = connectionManager.openConnection(this.url);        //使用connection进行操作        connectionManager.closeConnection();    }}

每个CURD方法都创立新的数据库连接会造成数据库的很大压力,这里可以有两种处理方案:

  1. 使用连接池管理连接,既不是每次都创立、销毁连接,而是从一个连接池里借出可用的连接,用完将其归还。

  2. 可以看到,这里connection的建立最好是这样的:每个线程希望有自己独立的连接来避免同步问题,在线程内部希望共用同一个连接来降低数据库的压力,那么使用ThreadLocal来管理数据库连接就是最好的选择了。它为每个线程维护了一个自己的连接,并且可以在线程内共享。

class ConnectionManager {    private static String url = System.getProperty("URL");    private static ThreadLocalconnectionHolder = ThreadLocal.withInitial(() -> {        try {            return DriverManager.getConnection(url);        } catch (SQLException e) {            e.printStackTrace();        }        return null;    });    public static Connection openConnection() {        return connectionHolder.get();    }    public static void closeConnection() {        Connection connect = connectionHolder.get();        if(connect!=null) {            try {                connect.close();            } catch (SQLException e) {                e.printStackTrace();            }        }    }}

另外还可以用到其余需要每个线程管理一份自己的资源副本的地方:An Introduction to ThreadLocal in Java

实现原理

这里面涉及到三种对象的映射:Thread-ThreadLocal对象-ThreadLocal中存的具体内容,既然是每个线程都会有一个资源副本,那么这个从ThreadLocal对象到存储内容的映射自然就会存在Thread对象里:

ThreadLocal.ThreadLocalMap threadLocals = null;

而ThreadLocal类只是提供了访问这个Map的接口:

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();}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是ThreadLocal的内部类,实现了一个相似HashMap的功能,其内部维护了一个Entry数组,下标就是通过ThreadLocal对象的threadLocalHashCode计算得来。这个Entry继承自WeakReference,实现对key,也就是ThreadLocal的弱引用:

static class Entry extends WeakReference<threadlocal    /** The value associated with this ThreadLocal. */    Object value;    Entry(ThreadLocal k, Object v) {        super(k);        value = v;    }}</threadlocal

内存模型图如下:

当ThreadLocal Ref出栈后,因为ThreadLocalMap中Entry对ThreadLocal只是弱引用,所以ThreadLocal对象会被回收,Entry的key会变成null,而后在每次get/set/remove ThreadLocalMap中的值的时候,会自动清除key为null的value,这样value也能被回收了。

注意:假如ThreadLocal Ref一直没有出栈(例如上面的connectionHolder,通常我们需要保证ThreadLocal为单例且全局可访问,所以设为static),具备跟Thread相同的生命周期,那么这里的虚引用便形同虚设了,所以使用完后记得调用ThreadLocal.remove将其对应的value清理。

另外,因为ThreadLocalMap中只对ThreadLocal是弱引用,对value是强引用,假如ThreadLocal由于没有其余强引用而被回收,之后也没有调用过get/set,那么就会产生内存泄露,

在使用线程池时,线程会被复用,那么里面保存的ThreadLocalMap同样也会被复用,会造成线程之间的资源没有被隔离,所以在线程归还回线程池时要记得调用remove方法。

hash冲突

上面提到ThreadLocalMap是自己实现的相似HashMap的功能,当出现Hash冲突(通过两个key对象的hash值计算得到同一个数组下标)时,它没有采用链表模式,而是采用的线性探测的方法,既当发生冲突后,就线性查找数组中空闲的位置。

当数组较大时,这个性能会很差,所以建议尽量控制ThreadLocal的数量。

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 面试大厂肯定离不开的——ThreadLocal,它的实现原理你知道吗?

发表回复