ThreadLocal

基础

ThreadLocal是一个关于创建线程局部变量的类。

通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。

有一个误区是ThreadLocal的目的是为了解决多线程访问资源时的共享问题 但ThreadLocal 并不解决多线程 共享 变量的问题。既然变量不共享,那就更谈不上同步的问题。

理解

ThreadLoal 变量,它的基本原理是,同一个 ThreadLocal 所包含的对象(对ThreadLocal< String >而言即为 String 类型变量),在不同的 Thread 中有不同的副本(实际是不同的实例)。这里有几点需要注意

  • 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来
  • 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题
  • 既无共享,何来同步问题,又何来解决同步问题一说?

那 ThreadLocal 到底解决了什么问题,又适用于什么样的场景?

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. 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).
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

核心意思是

ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。这让我想到了Js中的一个特性:闭包.闭包下的所有变量都是只属于自己的,而ThreadLocal就是只属于线程自己的对象. 另外,该场景下,并非必须使用 ThreadLocal ,其它方式完全可以实现同样的效果,只是 ThreadLocal 使得实现更简洁。

ThreadLocal用法

实例代码

下面通过如下代码说明 ThreadLocal 的使用方式

/**
 * @author Silence
 * @date 2018-08-0 18:19
 */
public class ThreadLocalDemo {
    public static void main(String[] args) throws InterruptedException {
        int threads = 3;
        //内部类 调用ThreadLocal 用于测试
        InnerClass innerClass = new InnerClass();
        for(int i = 1; i <= threads; i++) {
            new Thread(() -> {
                for(int j = 0; j < 4; j++) {
                    innerClass.add(String.valueOf(j));
                    innerClass.print();
                }
                innerClass.set("hello world");
            }, "thread - " + i).start();
        }
    }
    private static class InnerClass {
        public void add(String newStr) {
            StringBuilder str = Counter.threadLocal.get();
            Counter.threadLocal.set(str.append(newStr));
        }
        public void print() {
            System.out.printf("Thread name:%s , ThreadLocal hashcode:%s, StringBuffer hashcode:%s, Value:%s\n",
                    Thread.currentThread().getName(),
                    Counter.threadLocal.hashCode(),
                    Counter.threadLocal.get().hashCode(),
                    Counter.threadLocal.get().toString());
        }
        public void set(String words) {
            Counter.threadLocal.set(new StringBuilder(words));
            System.out.printf("Set, Thread name:%s , ThreadLocal hashcode:%s,  StringBuffer hashcode:%s, Value:%s\n",
                    Thread.currentThread().getName(),
                    Counter.threadLocal.hashCode(),
                    Counter.threadLocal.get().hashCode(),
                    Counter.threadLocal.get().toString());
        }
    }
    private static class Counter {
        /**
         * 创建StringBuffer类型的ThreadLocal
         */
        private static ThreadLocal threadLocal = ThreadLocal.withInitial(StringBuilder::new);
    }
}

实例分析

ThreadLocal本身支持范型。该例使用了 StringBuilder 类型的 ThreadLocal 变量。可通过 ThreadLocal 的 get() 方法读取 StringBuidler 实例,也可通过 set(T t) 方法设置 StringBuilder。

上述代码执行结果如下

Thread name:thread - 1 , ThreadLocal hashcode:569801221, StringBuffer hashcode:429380452, Value:0
Thread name:thread - 3 , ThreadLocal hashcode:569801221, StringBuffer hashcode:1134108604, Value:0
Thread name:thread - 2 , ThreadLocal hashcode:569801221, StringBuffer hashcode:2142643463, Value:0
Thread name:thread - 3 , ThreadLocal hashcode:569801221, StringBuffer hashcode:1134108604, Value:01
Thread name:thread - 1 , ThreadLocal hashcode:569801221, StringBuffer hashcode:429380452, Value:01
Thread name:thread - 3 , ThreadLocal hashcode:569801221, StringBuffer hashcode:1134108604, Value:012
Thread name:thread - 2 , ThreadLocal hashcode:569801221, StringBuffer hashcode:2142643463, Value:01
Thread name:thread - 3 , ThreadLocal hashcode:569801221, StringBuffer hashcode:1134108604, Value:0123
Thread name:thread - 1 , ThreadLocal hashcode:569801221, StringBuffer hashcode:429380452, Value:012
Set, Thread name:thread - 3 , ThreadLocal hashcode:569801221,  StringBuffer hashcode:1298065855, Value:hello world
Thread name:thread - 2 , ThreadLocal hashcode:569801221, StringBuffer hashcode:2142643463, Value:012
Thread name:thread - 1 , ThreadLocal hashcode:569801221, StringBuffer hashcode:429380452, Value:0123
Thread name:thread - 2 , ThreadLocal hashcode:569801221, StringBuffer hashcode:2142643463, Value:0123
Set, Thread name:thread - 1 , ThreadLocal hashcode:569801221,  StringBuffer hashcode:1735975136, Value:hello world
Set, Thread name:thread - 2 , ThreadLocal hashcode:569801221,  StringBuffer hashcode:1903281281, Value:hello world

从上面的输出可看出

  • 从第1-3行输出可见,每个线程通过 ThreadLocal 的 get() 方法拿到的是不同的 StringBuilder 实例 既每个线程都维护着属于自己的一个变量
  • 第1-3行输出表明,每个线程所访问到的是同一个 ThreadLocal 变量
  • 从7、12、13行输出以及输出代码可见,虽然从代码上都是对 Counter 类的静态 counter 字段进行 get() 得到 StringBuilder 实例并追加字符串,但是这并不会将所有线程追加的字符串都放进同一个 StringBuilder 中,而是每个线程将字符串追加进各自的 StringBuidler 实例内
  • 对比第1行与第15行输出并结合 Counter.counter.set(new StringBuilder(words)); 代码可知,使用 set(T t) 方法后,ThreadLocal 变量所指向的 StringBuilder 实例被替换

 

 

你可能感兴趣的:(Java基础)