Java-ThreadLocal (一)

在网上看了很多关于ThreadLocal的介绍,晕晕乎乎,终于算是搞清了Thread, ThreadLocalMap 和ThreadLocal三者的关系,赶紧记录以下自己的理解,以防忘记。

一、什么是ThreadLocal

我们写的Java代码可能会被多个线程并发执行,尤其是在Spring应用中。那么,我们在代码中写的变量,就是多个线程共享的,因此可能会造成一系列的线程安全问题,也因此出现了很多的解决方案,如加锁等,当然,这个不是这篇随笔的主题。

ThreadLocal就是为了存储一些线程私有的变量,从名字就能看出来。也就是说,这些变量不能被其他线程随便访问,只属于当前线程(可以共享给其子线程,后面会叙述)。至于应用场景,网上可以找到很多,就不记录了,目前我还没直接用过ThreadLocal。

二、ThreadLocal为什么是线程私有

首先要看一下ThreadLocal相关的类

有三个类: ThreadLocal,ThreadLocalMap,当然,还有Thread。

首先先说一下 ThreadLocal和ThreadLocalMap的关系

从代码结构来说,ThreadLocalMap是ThreadLocal的静态内部类:

public class ThreadLocal {
    
  【略】......
 
 static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
             Object value;
 
             Entry(ThreadLocal<?> k, Object v) {
                 super(k);
                 value = v;
             }
         }
private static final int INITIAL_CAPACITY = 16;
      private Entry[] table;
  【略】......    }
  【略】......

}

那么ThreadLocalMap又是啥?从名字看,似乎是存储ThreadLocal的Map,其实它就是。那么为什么它要写成ThreadLocal的内部类呢?总感觉有点怪。后面再说。

可以看到,ThreadLocalMap里面定义了Entry类,Entry里又有key又有value的,同时ThreadLocalMap里面还有一个Entry数组,是不是有HashMap内味儿了?那么,ThreadLocal就是这么个东西:

Java-ThreadLocal (一)

这里的threadlocal1 和threadlocal2 都是ThreadLocal的实例,也就是说,ThreadLocal只是作为key存在的,我们在一个线程里可以new很多个ThreadLocal,这样就可以存入很多个value。

那么,他们和Thread类是什么关系?

Thread类是Java中用来标志一个线程的类,里面有很多native方法来控制一个线程。从源码可以看出,Thread中是包含两个ThreadLocalMap变量的:

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

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

    【略】...
}

分别是 threadLocals 和 inheritableThreadLocals,什么区别后面再说。

这下他们三个的关系就建立了,大概可以这么表示:

Java-ThreadLocal (一)

从结构上来看,ThreadLocal似乎定义在Thread类里面更合适一些。那么为什么没有这么做呢?好像很少有人问到这个问题,我从*上看到一个回答,大致意思是说,ThreadLocal这个东西,并不是每个线程都会用得到的,如果把ThreadLocalMap写在Thread里面,每次新建线程都会被加载,那么开销将会变大。但是我个人感觉,可以为ThreadLocalMap单独写一个类,写成ThreadLocal的内部类总感觉。。。有点怪(*老哥说是个人风格)。

 三、写代码试试呗

1、ThreadLocal

从结果可以看出来,子线程是不能读到主线程的ThreadLocal数据的,而且在子线程里面set的数据,在主线程也读不到

public static void main(String[] args) throws InterruptedException {
        ThreadLocal<String> key1=new ThreadLocal<>();
        ThreadLocal<String> key2=new ThreadLocal<>();
        key1.set("val1");
        System.out.println("main thread "+key1.get());
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread1 "+key1.get());
                key2.set("val2");
                System.out.println("thread1 "+key2.get());
            }
        }).start();

        Thread.sleep(1000);  // 主线程休眠,让子线程先执行
        System.out.println("main thread "+key1.get());
        System.out.println("main thread "+key2.get());

    }

输出结果:

main thread val1
thread1 null
thread1 val2
main thread val1
main thread null

 

2、InheritableThreadLocal

把ThreadLocal换成InheriableThreadLocal。

结果有了一些变化,子线程可以读到主线程写入的val1了,然而子线程写入的val2在主线程还是不能读到。 也就是说,主线程是可以通过InheriableThreadLocal向子线程传数据的。

public static void main(String[] args) throws InterruptedException {
        ThreadLocal<String> key1=new InheritableThreadLocal<>(); // 这里变啦
        ThreadLocal<String> key2=new InheritableThreadLocal<>();
        key1.set("val1");
        System.out.println("main thread "+key1.get());
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread1 "+key1.get());
                key2.set("val2");
                System.out.println("thread1 "+key2.get());
            }
        }).start();

        Thread.sleep(1000);  // 主线程休眠,让子线程先执行
        System.out.println("main thread "+key1.get());
        System.out.println("main thread "+key2.get());

    }

输出结果:

main thread val1
thread1 val1
thread1 val2
main thread val1
main thread null

 

四、关于弱引用和内存泄漏

外卖到了,下次一定。

 

Java-ThreadLocal (一)

上一篇:Java 锁机制与线程调优


下一篇:python_java_go_c++_295. 数据流的中位数