ThreadLocal原理剖析

ThreadLocal提供线程局部变量。 这些变量与普通变量不同,因为每个访问一个线程(通过其get或set方法)的线程都有其自己的,独立初始化的变量副本。通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。 ThreadLocal实例通常是希望将状态与线程关联的类中的私有静态字段(例如,用户ID或事务ID)供线程局部变量。 这些变量与普通变量不同,因为每个访问一个线程(通过其get或set方法)的线程都有其自己的,独立初始化的变量副本。

使用方法

创建对象

ThreadLocal<String> threadLocal = new ThreadLocal<>();

set()方法

threadLocal.set("HelloWorld");

get()方法

threadLocal.get();

Example:

public class ThreadLocalTest {
    public static void main(String[] args) {
        ThreadLocal<OperatorContext> threadLocal = new ThreadLocal<>();

        Thread thread1 = new Thread(() -> {
            OperatorContext operatorContext = new OperatorContext();
            operatorContext.setUsername("xybh");
            operatorContext.setPassword("password");
            threadLocal.set(operatorContext);

            while (true) {
                System.out.println("thread1: " + threadLocal.get());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            OperatorContext operatorContext = new OperatorContext();
            operatorContext.setUsername("daodan");
            operatorContext.setPassword("root");
            threadLocal.set(operatorContext);

            while (true) {
                System.out.println("thread2: " + threadLocal.get());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread1.start();
        thread2.start();
    }

    static class OperatorContext {
        private String username;
        private String password;

        public OperatorContext() {}

        public String getUsername() { return username; }
        public void setUsername(String username) { this.username = username; }
        public String getPassword() { return password; }
        public void setPassword(String password) { this.password = password;}

        @Override
        public String toString() {
            return "OperatorContext{" +
                    "username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    '}';
        }
    }
}
thread1: OperatorContext{username='xybh', password='password'}
thread2: OperatorContext{username='daodan', password='root'}
thread1: OperatorContext{username='xybh', password='password'}
thread2: OperatorContext{username='daodan', password='root'}
thread1: OperatorContext{username='xybh', password='password'}
thread1: OperatorContext{username='xybh', password='password'}
thread2: OperatorContext{username='daodan', password='root'}

两个线程之间数据互不干扰

源码解析

为了更好的学习ThreadLocal的实现机制,必须去研究它的源码,了解它的实现机制。

类声明及创建方法

首先我们先看一下类的声明以及创建方法
ThreadLocal支持泛型,可以存储简单属性也可存储复合属性,常用于保存线程上下文。

public class ThreadLocal<T> {
    
    /**
     * Creates a thread local variable. The initial value of the variable is
     * determined by invoking the {@code get} method on the {@code Supplier}.
     *
     * @param <S> the type of the thread local's value
     * @param supplier the supplier to be used to determine the initial value
     * @return a new thread local variable
     * @throws NullPointerException if the specified supplier is null
     * @since 1.8
     */
    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }

    /**
     * Creates a thread local variable.
     * @see #withInitial(java.util.function.Supplier)
     */
    public ThreadLocal() {
    }
}

ThreadLocal中有两种实现方法,一种是未带任何实现的默认构造函数,一种是通过withInitial(Supplier<? extends S> supplier)方法,可通过实现Supplier接口产生带初始值的ThreadLocal对象。

get()和set()

接下来我们再看到ThreadLocal中最重要的两个方法get()和set()
首先我们先看到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对象,如果非空,那么取出ThreadLocal的value,否则进行初始化,初始化就是将initialValue的值set到ThreadLocal中。

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;
}

当ThreadLocal未调用set()方法或者为进行初始化的情况下,调用get()方法将会调用setInitialValue方法获取默认值,未重写initialValue方法的情况下,返回值为null,我们也可以重写initialValue方法来设置默认值

public class ThreadLocalTest {
    public static void main(String[] args) {
        new Thread(()->{
            StringThreadLocal stringThreadLocal = new StringThreadLocal();
            System.out.println(stringThreadLocal.get());
        }).start();

    }

    static class StringThreadLocal extends ThreadLocal<java.lang.String>{
        @Override
        protected java.lang.String initialValue() {
            return "init StringThreadLocal";
        }
    }
}

result:
result

接下来我们看下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);
}

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

set方法很简单,首先获取当前线程,再获取当前线程的ThreadLocalMap对象,如果ThreadLocalMap对象不为空,则覆盖当前ThreadLocal为key的值(并不是以当前对象为key),而当ThreadLocal为空时,则创建一个ThreadLocalMap对象将value赋值进去并将这个对象赋值给当前线程。

原理剖析

为什么ThreadLocal里的值只能单个线程使用

这个问题要看到ThreadLocalMap这个类,这个类创建后赋值给了当前线程的threadLocals,而threadLocals是每一个线程独享的,这使得ThreadLocal里的值只能单个线程使用。

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

ThreadLocal对象存放的位置

了解过JMM的同学应该清楚,在Java中,栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。可能很多同学会认为应该ThreadLocal的其他线程不可见,那么它应该是存在在栈内存中,其实不然,因为ThreadLocal实例实际上也是被其创建的类持有(更顶端应该是被线程持有)。而ThreadLocal的值其实也是被线程实例持有。只不过它使用了一些技巧使其对其他线程不可见,但它还是存放在堆内存中的。

ThreadLocal内存泄漏问题

之前阅读过一些文章,说ThreadLocal存在内存泄漏问题,在使用完ThreadLocal后需要手动将其关闭,这里我们通过阅读源码发现:真正用来存储值的对象是ThreadLocalMap中的Entry,而它是一个弱引用对象。

弱引用: 在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
如果一个对象是偶尔(很少)的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用Weak Reference来记住此对象。

因为ThreadLocalMap在选择key的时候,并不是直接选择ThreadLocal实例,而是ThreadLocal实例的弱引用。所以实际上从ThreadLocal设计角度来说是不会导致内存泄露的。

参考

【1】理解Java中的ThreadLocal——技术小黑屋
【2】ThreadLocal详解——神一般的存在

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值