| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Java知识库 -> 一篇文章搞懂 ThreadLocal -> 正文阅读 |
|
[Java知识库]一篇文章搞懂 ThreadLocal |
ThreadLocal 如何保证对象只被当前线程访问呢? 下面让我们一起深入 ThreadLocal 的内部实现。 我们需要关注的自然是 ThreadLocal 的 set() 方法和 get() 方法。 set先从 set() 方法说起: ? ? /** ? ? * Sets the current thread's copy of this thread-local variable ? ? * to the specified value. Most subclasses will have no need to ? ? * override this method, relying solely on the {@link #initialValue} ? ? * method to set the values of thread-locals. ? ? * ? ? * @param value the value to be stored in the current thread's copy of ? ? * ? ? ? this thread-local. ? ? */ ? public void set(T value) { ? ? ? Thread t = Thread.currentThread(); ? ? ? ThreadLocalMap map = getMap(t); ? ? ? if (map != null) ? ? ? ? ? map.set(this, value); ? ? ? else ? ? ? ? ? createMap(t, value); ? } 在 set 时,首先获得当前线程对象,然后通过 getMap() 方法拿到线程的 ThreadLocalMap ,并将值存入 ThreadLocalMap 中。 而 ThreadLocalMap 可以理解为一个 Map (虽然不是,但是你可以把它简单地理解成 HashMap ),但是它是定义在 Thread 内部的成员。 注意下面的定义是从 Thread 类中摘出来的 ? /* ThreadLocal values pertaining to this thread. This map is maintained ? ? * by the ThreadLocal class. */ ? ThreadLocal.ThreadLocalMap threadLocals = null; 而设置到 ThreadLocal 中的数据,也正是写入了 threadLocals 的这个 Map 。 其中, key 为 ThreadLocal 当前对象, value 就是我们需要的值。 而 threadLocals 本身就保存了当前自己所在线程的所有“局部变量”,也就是一个 ThreadLocal 变量的集合。 在这里也给想提升的开发人们安利一个福利:Java高级进阶笔记,完整版PDF文档点击此处免费领取。 get在进行 get() 方法操作时,自然就是将这个 Map 中的数据拿出来。 ? ? ? /** ? ? * Returns the value in the current thread's copy of this ? ? * thread-local variable. If the variable has no value for the ? ? * current thread, it is first initialized to the value returned ? ? * by an invocation of the {@link #initialValue} method. ? ? * ? ? * @return the current thread's value of this thread-local ? ? */ ? 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 对象,然后通过将自己作为 key 取得内部的实际数据。 Thread.exit()在了解了 ThreadLocal 的内部实现后,我们自然会引出一个问题: 那就是这些变量是维护在 Thread 类内部的( ThreadLocalMap 定义所在类),这也意味着只要线程不退出,对象的引用将一直存在。 当线程退出时, Thread 类会进行一些清理工作,其中就包括清理 ThreadLocalMap 。 ? ? /** ? ? * This method is called by the system to give a Thread ? ? * a chance to clean up before it actually exits. ? ? */ ? private void exit() { ? ? ? if (group != null) { ? ? ? ? ? group.threadTerminated(this); ? ? ? ? ? group = null; ? ? ? } ? ? ? /* Aggressively null out all reference fields: see bug 4006245 */ ? ? ? target = null; ? ? ? /* Speed the release of some of these resources */ ? ? ? threadLocals = null; ? ? ? inheritableThreadLocals = null; ? ? ? inheritedAccessControlContext = null; ? ? ? blocker = null; ? ? ? uncaughtExceptionHandler = null; ? } 因此,使用线程池就意味着当前线程未必会退出(比如固定大小的线程池,线程总是存在)。 如果这样,将一些大的对象设置到 ThreadLocal 中(它实际保存在线程持有的 ThreadLocalMap 内),可能会使系统出现内存泄漏的可能。 这里我的意思是:你设置了对象到 Threadlocal 中,但是不清理它,在你使用几次后,这个对象也不再有用了,但是它却无法被回收。 此时,如果你希望及时回收对象,最好使用 ThreadLocal.remove() 方法将这个变量移除,就像我们习惯性地关闭数据库连接一样。 如果你确实不需要这个对象了,就应该告诉虚拟机,请把它回收,防止内存泄漏。 tl = null另外一种有趣的情况是 JDK 也可能允许你像释放普通变量一样释放 ThreadLocal 。 比如,我们有时候为了加速垃圾回收,会特意写出类似 obj = null 的代码。 如果这么做,那么 obj 所指向的对象就会更容易地被垃圾回收器发现,从而加速回收。 同理,如果对于 ThreadLocal 的变量,我们也手动将其设置为 null ,比如 tl = null ,那么这个 ThreadLocal 对应的所有线程的局部变量都有可能被回收。 这里面的奥秘是什么呢? 先来看一个简单的例子。 package com.shockang.study.java.concurrent.thread_local; ? import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; ? public class ThreadLocalDemo_Gc { static volatile ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>() { protected void finalize() throws Throwable { System.out.println(this.toString() + " is gc"); } }; static volatile CountDownLatch cd = new CountDownLatch(10000); ? public static class ParseDate implements Runnable { int i = 0; ? public ParseDate(int i) { this.i = i; } ? public void run() { try { if (tl.get() == null) { tl.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") { protected void finalize() throws Throwable { System.out.println(this.toString() + " is gc"); } }); System.out.println(Thread.currentThread().getId() + ":create SimpleDateFormat"); } Date t = tl.get().parse("2015-03-29 19:29:" + i % 60); } catch (ParseException e) { e.printStackTrace(); } finally { cd.countDown(); } } } ? public static void main(String[] args) throws InterruptedException { ExecutorService es = Executors.newFixedThreadPool(10); for (int i = 0; i < 10000; i++) { es.execute(new ParseDate(i)); } cd.await(); System.out.println("mission complete!!"); tl = null; System.gc(); System.out.println("first GC complete!!"); //在设置ThreadLocal的时候,会清除ThreadLocalMap中的无效对象 tl = new ThreadLocal<SimpleDateFormat>(); cd = new CountDownLatch(10000); for (int i = 0; i < 10000; i++) { es.execute(new ParseDate(i)); } cd.await(); Thread.sleep(1000); ? System.gc(); System.out.println("second GC complete!!"); ? } } 上述案例是为了跟踪 ThreadLocal 对象,以及内部 SimpleDateFormat 对象的垃圾回收。 为此,我们重载了 finalize() 方法。 这样,我们在对象被回收时,就可以看到它们的踪迹。 在主函数 main 中,先后进行了两次任务提交,每次 10000 个任务。 在第一次任务提交后,我们将 tl 设置为 null ,并进行一次 GC 。 接着,我们进行第二次任务提交,完成后,再进行一次 GC 。 执行上述代码,最有可能的一种输出如下所示。 19:create SimpleDateFormat 15:create SimpleDateFormat 17:create SimpleDateFormat 18:create SimpleDateFormat 20:create SimpleDateFormat 14:create SimpleDateFormat 11:create SimpleDateFormat 12:create SimpleDateFormat 13:create SimpleDateFormat 16:create SimpleDateFormat mission complete!! first GC complete!! com.shockang.study.java.concurrent.thread_local.ThreadLocalDemo_Gc$1@5041865d is gc 11:create SimpleDateFormat 14:create SimpleDateFormat 20:create SimpleDateFormat 12:create SimpleDateFormat 16:create SimpleDateFormat 13:create SimpleDateFormat 18:create SimpleDateFormat 15:create SimpleDateFormat 17:create SimpleDateFormat 19:create SimpleDateFormat second GC complete!! 注意这些输出所代表的含义。 首先,线程池中 10 个线程都各自创建了一个 SimpleDateFormat 对象实例。 接着进行第一次 GC ,可以看到 ThreadLocal 对象被回收了(这里使用了匿名类,所以类名看起来有点怪,这个类就是开头创建的 t 对象)。 提交第 2 次任务,这次一样也创建了 10 个 SimpleDateFormat 对象,然后进行第二次 GC 。 在第二次 GC 后,第一次创建的 10 个 SimpleDateFormat 的子类实例全部被回收。 虽然我们没有手工 remove (这些对象,但是系统依然有可能回收它们)。 ThreadLocal.ThreadLocalMap要了解上面的回收机制,我们需要更进一步了解 ThreadLocal.ThreadLocalMap 的实现。 之前我们说过, ThreadLocalMap 是一个类似 HashMap 的东西。 更准确地说,它更加类似 WeakHashMap。 ThreadLocalMap 的实现使用了弱引用。 弱引用是比强引用弱得多的引用。 Java 虚拟机在垃圾回收时,如果发现弱引用,就会立即回收。 ThreadLocalMap 内部由一系列 Entry 构成,每一个 Entry 都是 WeakReference< ThreadLocal>。 ? ? ? /** ? ? ? ? * 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; ? ? ? ? ? } ? ? ? } 这里的参数 k 就是 Map 的 key , v 就是 Map 的 value ,其中 k 也是 ThreadLocal 实例,作为弱引用使用。 super(k) 就是调用了 WeakReference 的构造函数 因此,虽然这里使用 ThreadLocal 作为 Map 的 key ,但是实际上,它并不真的持有 Threadlocal 的引用。 而当 ThreadLocal 的外部强引用被回收时, ThreadLocalMap 中的 key 就会变成 null 。 当系统进行 ThreadLocalMap 清理时(比如将新的变量加入表中,就会自动进行一次清理,虽然 JDK 不定会进行一次彻底的扫描,但显然在这个案例中,它奏效了),就会将这些垃圾数据回收。 ThreadLocal 的回收机制ThreadLocal 的回收机制,如图所示。 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/24 2:51:09- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |