News:三分天注定,七分靠打拼,爱拼才会赢!致力打造专业IT博客。如果你对本博客有任何意见或建议请联系作者,邮箱:blog@mymail.com.cn

ThreadLocal 分析

逝水无痕 523 0 条

ThreadLocal 并不是一个 Thread ,而是主要用来提供线程局部变量,也就是变量只对当前线程可见。线程局部变量功用其实非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是 Java 中一种比较特殊的线程绑定机制,使每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。ThreadLocal 一个比较常见的应用是将资源与当前线程绑定,比如 Spring 的事务实现就会将 Transaction 资源与当前线程绑定从而解决事务并发问题。

java.gif

下面将通过一个实例来分析 ThreadLocal 能提供线程局部变量的原理。

User 类

public class User {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

一个自定义的 ThreadLocal 子类

public class MyThreadLocal extends ThreadLocal<User> {
    
    @Override
    protected User initialValue() {
        return new User();
    }

}

一个测试类

public class Test {
    
    private static MyThreadLocal myt = new MyThreadLocal();

    public static void main(String[] args) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("1" + Test.myt.get());
                System.out.println("2" + Test.myt.get());
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("3" + Test.myt.get());
            }
        }).start();

    }
}

运行 main 方法的结果

1com.example.demo.test.User@29ed326f
1com.example.demo.test.User@29ed326f
2com.example.demo.test.User@12f4b14e

可以看到两个线程输出的结果不一样:第一个线程两次取到的线程变量一致,而第二个线程取到的线程变量与第一个线程取到的不一致。这也就印证了 ThreadLocal 能提供线程局部变量,而且变量只对当前线程可见。

分析

从 main 方法中可以看到它运行了两个线程,而且这两个线程执行的代码非常相似,都是调用的本类中的静态类型的变量 myt 的 get() 方法,但是输出的结果却不一样。下面从 myt.get() 方法的源码入手开始分析,这个 get() 方法是 ThreadLocal 类中的方法:

public T get() {
    Thread t = Thread.currentThread();

    ThreadLocalMap map = getMap(t); // 这个 ThreadLocalMap 是当前线程对象中的 threadLocals 属性,初始为 null

    if (map != null) { // 因为初始时是 null 所以第一次调用 get() 方法时不执行这里的代码

        ThreadLocalMap.Entry e = map.getEntry(this); // 这里是用 ThreadLocal 对象作为 key 来取值

        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue(); // 执行这个方法
}

下面是 get() 方法中调用的 getMap(t) 方法的源码:

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

下面是 get() 方法中调用的 setInitialValue() 方法的源码:

private T setInitialValue() {

    T value = initialValue(); // 这个 initialValue() 执行的是我们自定义的 MyThreadLocal 类中的 initialValue() 方法,该方法返回了我们 new 的 User 对象

    Thread t = Thread.currentThread();

    ThreadLocalMap map = getMap(t); // 这个 map 是 null
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value); // 这个是关键方法

    return value; // 将 new 的 User 对象返回
}

下面是 setInitialValue() 方法中调用的 createMap(t, value) 方法的源码:

void createMap(Thread t, T firstValue) {

    t.threadLocals = new ThreadLocalMap(this, firstValue); // 创建了一个 ThreadLocalMap 对象,其中的this 参数指的是 ThreadLocal 对象,firstValue 指的是我们 new 的 User 对象。将这个创建好的 ThreadLocalMap 对象赋值给了当前线程对象中的 threadLocals 属性。

}

从上面的源码中可以分析出:第一个线程在第一次调用 get() 方法时由于当前线程对象的 threadLocals 属性为 null 所以调用了我们自定义的 MyThreadLocal 类中的 initialValue() 创建了一个新 User 对象,然后初始化了 threadLocals 属性,同时将 myt 对象当作 key 、User 对象当作 value 放入了 threadLocals 这个 ThreadLocalMap 中,最后返回了我们新建的 User 对象;当第一个线程在第二次调用 get() 方法时,由于当前线程对象的 threadLocals 属性已经不是 null 了,所以返回了 threadLocals 中以 myt 对象为 key 的取到的值,也就是我们原来创建的那个 User 对象,这也就是第一个线程两次调用 get() 方法结果相同的原因。

第二个线程的执行过程与第一个线程的执行过程完全一样,但是由于 threadLocals 这个关键 ThreadLocalMap 属性却是每个线程对象所独有的,所以即使有相同的 myt key值,取出的结果却不一样。

以上的分析就是 ThreadLocal 能够提供线程局部变量的原因。如果你对 ThreadLocal 的实现原理比较模糊,希望这篇文章能对你有所帮助。

发表我的评论
icon_mrgreen.gificon_neutral.gificon_twisted.gificon_arrow.gificon_eek.gificon_smile.gificon_confused.gificon_cool.gificon_evil.gificon_biggrin.gificon_idea.gificon_redface.gificon_razz.gificon_rolleyes.gificon_wink.gificon_cry.gificon_surprised.gificon_lol.gificon_mad.gificon_sad.gificon_exclaim.gificon_question.gif

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址