ThreadLocal的原理

ThreadLocal是一个支持泛型的java类,抛开里面的静态内部类ThreadLocalMap不说,其实它没几行代码,不信,您自己去看看。它用来干啥?类上注释说的很明白:

  • 它能让线程拥有了自己内部独享的变量
  • 每一个线程可以通过get、set方法去进行操作
  • 可以覆盖initialValue方法指定线程独享的值
  • 通常会用来修饰类里private static final的属性,为线程设置一些状态信息,例如user ID或者Transaction ID
  • 每一个线程都有一个指向threadLocal实例的弱引用,只要线程一直存活或者该threadLocal实例能被访问到,都不会被垃圾回收清理掉。

话不多说,我们来看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();
    }
  • 获取当前线程内部的ThreadLocalMap
  • map存在则获取当前ThreadLocal对应的value值
  • map不存在或者找不到value值,则调用setInitialValue,进行初始化
setInitialValue()源码
  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;
    }
  • 调用initialValue方法,获取初始化值【调用者通过覆盖该方法,设置自己的初始化值】
  • 获取当前线程内部的ThreadLocalMap
  • map存在则把当前ThreadLocal和value添加到map中
  • map不存在则创建一个ThreadLocalMap,保存到当前线程内部
小结

每一个线程都有一个私有变量,是ThreadLocalMap类型。当为线程添加ThreadLocal对象时,就是保存到这个map中,所以线程与线程间不会互相干扰。总结起来,一句话:我有我的map。

神奇的remove

因为ThreadLocal使用不当,会引发内存泄露的问题

示例一:
public class MemoryLeak {

     public static void main(String[] args) {
         new Thread(new Runnable() {
             @Override
             public void run() {
                 for (int i = 0; i < 1000; i++) {
                     TestClass t = new TestClass(i);
                     t.printId();
                    t = null;
                }
           }
        }).start();
    }

    static class TestClass{
        private int id;
        private int[] arr;
        private ThreadLocal<TestClass> threadLocal;
        TestClass(int id){
            this.id = id;
            arr = new int[1000000];
            threadLocal = new ThreadLocal<>();
            threadLocal.set(this);
        }

        public void printId(){
            System.out.println(threadLocal.get().id);
        }
    }
}

运行结果:

0
1
2
3
5...省略...
440
441
442
443
444
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
    at com.gentlemanqc.MemoryLeak$TestClass.<init>(MemoryLeak.java:33)
    at com.gentlemanqc.MemoryLeak$1.run(MemoryLeak.java:16)
    at java.lang.Thread.run(Thread.java:745)

对上述代码稍作修改,请看:

public class MemoryLeak {

     public static void main(String[] args) {
         new Thread(new Runnable() {
             @Override
             public void run() {
                 for (int i = 0; i < 1000; i++) {
                     TestClass t = new TestClass(i);
                     t.printId();
                     t.threadLocal.remove();
                }
           }
        }).start();
    }

    static class TestClass{
        private int id;
        private int[] arr;
        private ThreadLocal<TestClass> threadLocal;
        TestClass(int id){
            this.id = id;
            arr = new int[1000000];
            threadLocal = new ThreadLocal<>();
            threadLocal.set(this);
        }

        public void printId(){
            System.out.println(threadLocal.get().id);
        }
    }
}

运行结果:

0
1
2
3
...省略...
996
997
998
999

一个内存泄漏,一个正常完成,对比代码只有一处不同:t = null改为了t.threadLocal.remove();哇,神奇的remove!!!

set(T value)源码
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
  • map存在则把当前ThreadLocal和value添加到map中
  • map不存在则创建一个ThreadLocalMap,保存到当前线程内部
remove源码
 public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

就一句话,获取当前线程内部的ThreadLocalMap,存在则从map中删除这个ThreadLocal对象。

无处不在的ThreadLocalMap

  • ThreadLocalMap是一个自定义的hash map,专门用来保存线程的thread local变量
  • 它的操作仅限于ThreadLocal类中,不对外暴露
  • 这个类被用在Thread类的私有变量threadLocals和inheritableThreadLocals上
  • 为了能够保存大量且存活时间较长的threadLocal实例,hash table entries采用了WeakReferences作为key的类型
  • 一旦hash table运行空间不足时,key为null的entry就会被清理掉

当创建一个ThreadLocalMap时,实际上内部是构建了一个Entry类型的数组,初始化大小为16,阈值threshold为数组长度的2/3,Entry类型为WeakReference,有一个弱引用指向ThreadLocal对象。

为什么Entry采用WeakReference类型?

Java垃圾回收时,看一个对象需不需要回收,就是看这个对象是否可达。什么是可达,就是能不能通过引用去访问到这个对象。(当然,垃圾回收的策略远比这个复杂,这里为了便于理解,简单给大家说一下)。

jdk1.2以后,引用就被分为四种类型:强引用、弱引用、软引用和虚引用。强引用就是我们常用的Object obj = new Object(),obj就是一个强引用,指向了对象内存空间。当内存空间不足时,Java垃圾回收程序发现对象有一个强引用,宁愿抛出OutofMemory错误,也不会去回收一个强引用的内存空间。而弱引用,即WeakReference,意思就是当一个对象只有弱引用指向它时,垃圾回收器不管当前内存是否足够,都会进行回收。反过来说,这个对象是否要被垃圾回收掉,取决于是否有强引用指向。ThreadLocalMap这么做,是不想因为自己存储了ThreadLocal对象,而影响到它的垃圾回收,而是把这个主动权完全交给了调用方,一旦调用方不想使用,设置ThreadLocal对象为null,内存就可以被回收掉。

原文地址:https://www.cnblogs.com/cq-yangzhou/p/11327197.html

时间: 2024-10-22 17:23:34

ThreadLocal的原理的相关文章

ThreadLocal的原理、作用、使用弱引用原因、应用举例

一. 原理 ThreadLocal就是一个类,他有get.set方法,可以起到一个保存.获取某个值的作用.但是这个类的get.set方法有点特殊,各个线程调用他的get.set操作是互不干扰的,具体原因在于他的方法实现: public T get() { Thread t = Thread.currentThread(); //先确定调用我的线程 ThreadLocalMap map = getMap(t); //根据调用我的线程,找到这个线程的ThreadLocalMap对象 if (map

ThreadLocal实现原理

一.ThreadLocal介绍 这是一个线程的局部变量.也就是说,只有当前线程可以访问.既然是只有当前线程可以访问的数据,自然是线程安全的. 为每一个线程分配不同的对象,需要在应用层面保证.ThreadLocal只是起到了简单的容器作用. 二.实现原理 1. 我们需要关注的是ThreadLocal的set()方法和get()方法. set()方法 public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMa

ThreadLocal 工作原理、部分源码分析

1.大概去哪里看 ThreadLocal 其根本实现方法,是在Thread里面,有一个ThreadLocal.ThreadLocalMap属性 ThreadLocal.ThreadLocalMap threadLocals = null; ThreadLocalMap 静态内部类维护了一个Entry 数组 private Entry[] table; 查看Entry 源码,它维护了两个属性,ThreadLocal 对象 与一个Object static class Entry extends W

对ThreadLocal实现原理的一点思考

前言 在<透彻理解Spring事务设计思想之手写实现>中,已经向大家揭示了Spring就是利用ThreadLocal来实现一个线程中的Connection是同一个,从而保证了事务.本篇博客将带大家来深入分析ThreadLocal的实现原理. ThreadLocal是什么.有什么.能做什么? ThreadLocal提供一个线程(Thread)局部变量,访问到某个变量的每一个线程都拥有自己的局部变量.说白了,ThreadLocal就是想在多线程环境下去保证成员变量的安全. ThreadLocal提

ThreadLocal设计原理

1. ThreadLocal 1.1 简介 ThreadLocal是线程内部的数据存储类,通过它可以指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取数据. 它能够满足以下需求: 同一个变量在不同的线程中需要有不同的副本 经常应用于static方法,无法在线程创建的时候赋值 1.2 应用场景 数据库连接池 Hibernate的session 其他线程不安全的类在多线程中不加锁使用 1.3 应用示例 时间解析类SimpleDateFormat是线程

ThreadLocal 应用原理解析与常见问题

ThreadLocal是大家比较常用到的,在多线程下存储线程相关数据十分合适.可是很多时候我们并没有深入去了解它的原理. 首选提出几个问题,稍后再针对这些问题一一解答. 提到ThreadLocal,大家常说ThreadLocal是弱引用,那么ThreadLocal究竟是如何实现弱引用的呢? ThreadLocal是如何做到可以当做线程局部变量的呢? 大家创建ThreadLocal变量时,为什么都要用static修饰? 大家争论不止的ThreadLocal内存泄漏是什么鬼? 进入正题,先简单了解下

Java中ThreadLocal无锁化线程封闭实现原理

虽然现在可以说很多程序员会用ThreadLocal,但是我相信大多数程序员还不知道ThreadLocal,而使用ThreadLocal的程序员大多只是知道其然而不知其所以然,因此,使用ThreadLocal的程序员很多时候会被它导入到陷进中去,其实java很多高级机制系列的很多东西都是一把双刃剑,也就是有利必有其弊,那么我们的方法是找到利和弊的中间平衡点,最佳的方式去解决问题. 本文首先说明ThreadLocal能做什么,然后根据功能为什么要用它,如何使用它,最后通过内部说明讲解他的坑在哪里,使

java ThreadLocal(应用场景及使用方式及原理)

尽管ThreadLocal与并发问题相关,可是很多程序猿只将它作为一种用于"方便传參"的工具,胖哥觉得这或许并非ThreadLocal设计的目的,它本身是为线程安全和某些特定场景的问题而设计的. ThreadLocal是什么呢. 每一个ThreadLocal能够放一个线程级别的变量,可是它本身能够被多个线程共享使用,并且又能够达到线程安全的目的,且绝对线程安全. 比如: public final static ThreadLocal<String> RESOURCE = n

ThreadLocal使用以及原理

介绍 ThreadLocal是一个用于创建线程局部变量的类.当前线程通过ThreadLocal的set()方法设置的变量只对当前线程可见,通过get()获取设置的变量. 使用 支持泛型 ThreadLocal<String> threadLocal = new ThreadLocal<>(); 当前线程通过ThreadLocal对象的set(value)/get()设置变量和获取设置的变量 threadLocal.set("jinshuai"); threadL