ThreadLocal源码解析

前言

本篇文章希望能够抛砖引玉,如有不对之处,还希望你能留下宝贵的意见,供我继续学习。不必吝啬批评指正,感激不尽~

ThreadLocal是什么

声明:本篇文章采用的是JDK1.8

从官方源码给出的解释来看:

/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).

结合我的理解:这个类提供的是一个线程(thread)能够独立使用的局部变量,这个变量不能被其他线程访问,而该线程也只能够通过get或者set方法访问。简单总结就是线程层面的数据隔离。

什么时候会使用到ThreadLocal

数据库连接

当我们在一个Web应用中,每个HTTP请求通常都在自己的线程中处理。如果在处理请求的过程中需要访问数据库,那么每个请求都需要自己的数据库连接。如果我们不使用ThreadLocal,而是选择在一个公共的地方存储数据库连接,那么可能会出现多个线程使用同一个数据库连接的情况,这可能会导致数据的混乱和错误。

使用ThreadLocal可以很好地解决这个问题。我们可以在每个线程中创建一个新的数据库连接,并将这个连接存储在ThreadLocal中。这样,每个线程都可以通过ThreadLocal获取到自己的数据库连接,而不会影响到其他线程。

下面这段代码摘自:

https://www.cnblogs.com/dolphin0520/p/3920407.html

private static ThreadLocal<Connection> connectionHolder
= new ThreadLocal<Connection>() {
    public Connection initialValue() {
        return DriverManager.getConnection(DB_URL);
    }
};

public static Connection getConnection() {
    return connectionHolder.get();
}

浏览器Cookie和Session管理

  • 每当我访问一个页面的时候,浏览器都会帮我们从硬盘中找到对应的Cookie发送过去。
  • 浏览器是十分聪明的,不会发送别的网站的Cookie过去,只带当前网站发布过来的Cookie过去

在Web应用中,ThreadLocal常常被用于处理HTTP请求中的用户会话和Cookie信息。每个HTTP请求通常都在自己的线程中处理,因此,每个请求都需要自己的会话和Cookie信息。如果我们不使用ThreadLocal,而是选择在一个公共的地方存储会话和Cookie信息,那么可能会出现多个线程使用同一个会话或Cookie的情况,这可能会导致数据的混乱和错误。

下面这段代码摘自:

https://www.cnblogs.com/dolphin0520/p/3920407.html

private static final ThreadLocal threadSession = new ThreadLocal();

public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
    if (s == null) {
        s = getSessionFactory().openSession();
        threadSession.set(s);
    }
} catch (HibernateException ex) {
    throw new InfrastructureException(ex);
}
return s;
}

使用示例

public class ThreadLocalDemo {
    ThreadLocal<String> threadLocal = new ThreadLocal<String>();
    public static void main(String[] args) {
        ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo();
        threadLocalDemo.showTowThread();
    }
    //这里Runnable是接口,如果想创建一个Thread建议使用这种方式,避免多继承造成的问题
    //回顾知识点下面的匿名类是如何使用的?
    public void showTowThread() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set("Thread1");
                System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set("Thread2");
                System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());
            }
        }).start();
    }
}

输出结果:

Thread-0 Thread1
Thread-1 Thread2

ThreadLocal实现原理

声明:本文采用的是JDK1.8

1. set方法

刚才我们在示例是不是有个threadLocal.set("Thread1"),看看源代码

public void set(T value) {
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取当前线程的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    //如果map不为空,直接设置值
    if (map != null) {
        map.set(this, value);
    } else {
    //如果为空,创建一个新的map并设值    
        createMap(t, value);
    }
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

这里引入了一个新的对象ThreadLocalMap,先看源码的给出的注释

    /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
 static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
     //..省略代码

通过上面我们可以发现的是ThreadLocalMap是ThreadLocal的一个内部类。用Entry类来进行存储。

我们的值都是存储到这个Map上的,key是当前ThreadLocal对象

我们又在Thread的局部变量里找到ThreadLocalMap的引用:

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

那么我们是不是就可以这样一个结论:Thread为每个线程维护了一个ThreadLocalMap这么一个Map,这个Map的key是当前线程的ThreadLocal对象本身,value就是你需要设置的这个值。

2.get方法

public T get() {
//跟set()同样的操作
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//如果map不为空,直接返回值
if (map != null) {
//Entry可以理解为那个map,源码里可以详细看到
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings(“unchecked”)
T result = (T)e.value;
return result;
}
}
//如果map为空,创建一个新的map并设值
return setInitialValue();
}

这里可以看到setget都进行的操作就是获取当前线程,那么这不就保证了当前线程的ThreadLocalMap只能由当前线程操作,可不就是线程级的数据隔离吗。

避免内存泄漏

ThreadLocal的对象关系引用图:

public class ThreadLocalForOOM {
    /**
     * -Xms50m -Xmx50m
     */
    static class OOMObject {
        private Long[] a = new Long[2 * 1024 * 1024];
    }

    final static ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 5, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

    final static ThreadLocal<OOMObject> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            pool.execute(() -> {
                threadLocal.set(new OOMObject());
                System.out.println("oom object--->" + finalI);
                OOMObject oomObject = threadLocal.get();
                System.out.println("oomObject---->" + oomObject);
                // threadLocal.remove(); // 记得remove 防止内存泄露,此时一定要在使用完remove
            });

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

这里报错:

java.lang.OutOfMemoryError: Java heap space
Exception in thread "pool-1-thread-20" java.lang.OutOfMemoryError: Java heap space

ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用

想要避免内存泄露就要手动remove()掉

注意:

  • 首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。
  • 另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。

总结

本文只是对ThreadLocal浅显的探讨,也许你还可以找到更有趣的比如:table扩容清除方式解决hash冲突。如果本文有讲述不对的地方,也希望你能留下宝贵的指正,感激不尽~

参考博文:

https://juejin.cn/post/6844903586984361992

https://www.cnblogs.com/zhangjk1993/p/6641745.html#_label2

https://www.iteye.com/topic/103804

https://juejin.cn/post/7218564871830945848#heading-3

https://www.cnblogs.com/dolphin0520/p/3920407.html

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇