java之ThreadLocal

一 .see see 官网

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).-----来自官网

翻译:ThreadLocal类用来提供thread-local变量的。thread-local变量不同于那些标准的变量,每个线程通过get或者set方法访问它都会获取各自线程自己的变量副本.ThreadLocal实例通常来说都是private static类型(== 通常,我们需要保证ThreadLocal能够被全局访问到,同时也必须保证其为单例,因此,在一个类中将其设为static类型便成为了惯用做法,因为ThreadLocal所要保存的value,其实是保存在当前Thread所维护的map中,而key是ThreadLocal ==)。
总结:官方的这句话翻译起来真费劲,我的理解是thread-local变量能够保存各自线程的变量,而不会影像其他线程的变量.可以用这样的场景理解下,在springboot应用中,service中处理一段业务要将结果临时存储下供后续的逻辑继续使用,我们知道service默认情况下都是单例的,如果放在这个类的变量存储势必会造成多线程访问的任意修改,这样很明显是我们不想看到的,这样其实我们就可以使用thread-local变量进行存储,多线程下自己存储自己的,互不影响.

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实例可以访问,每个线程都会持有thread-local变量副本的隐式引用; 线程消失后,ThreadLocal实例所有的副本都将被GC回收(除非还存在对这些副本的其他引用).一句话,只要线程一挂,ThreadLocal中所保存的变量都将被GC回收.

T 	get() 
//Returns the value in the current thread's copy of this thread-local variable.
protected T 	initialValue()
//Returns the current thread's "initial value" for this thread-local variable.
void 	remove()
//Removes the current thread's value for this thread-local variable.
void 	set(T value)
//Sets the current thread's copy of this thread-local variable to the specified value.
static <S> ThreadLocal<S> 	withInitial(Supplier<? extends S> supplier)
//Creates a thread local variable.

==get():==获取与当前线程关联的ThreadLocal值。

==set(T value):==设置与当前线程关联的ThreadLocal值。

== initialValue()方法:==设置与当前线程关联的ThreadLocal初始值。

This method will be invoked the first time a thread accesses
the variable with the get() method, unless the thread previously
invoked the set(T) method, in which case the initialValue method will
not be invoked for the thread. Normally, this method is invoked at
most once per thread, but it may be invoked again in case of
subsequent invocations of remove() followed by get(). This
implementation simply returns null; if the programmer desires
thread-local variables to have an initial value other than null,
ThreadLocal must be subclassed, and this method overridden. Typically,
an anonymous inner class will be used.

当调用get()方法的时候,若是与当前线程关联的ThreadLocal值调用 set(T)已经被设置过,则不会调用initialValue()方法;否则,会调用initialValue()方法来进行初始值的设置。通常每个线程中initialValue()方法只会被调用一次,除非调用了remove()方法之后又调用get()方法;如果程序需要 thread-local 变量有一个初始值,则需要在子类中重载initialValue(),或者是通过匿名内部类的方式实现。官方的demo可以参考下,如下:

import java.util.concurrent.atomic.AtomicInteger;
 public class ThreadId {
     // Atomic integer containing the next thread ID to be assigned
     private static final AtomicInteger nextId = new AtomicInteger(0);
     // Thread local variable containing each thread's ID
     private static final ThreadLocal<Integer> threadId =
         new ThreadLocal<Integer>() {
             @Override protected Integer initialValue() {
                 return nextId.getAndIncrement();
         }
     };  
 }

二 .跑个demo

 static class MyThread extends Thread {

        private static ThreadLocal<UUID> threadLocal = new ThreadLocal<>();

        @Override
        public void run() {
            //让线程Thread1等一等,这样能够保证Thread1的set 能将Thread2的set覆盖掉,
            if (getName().equals("Thread1")) {

                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {

                }
            }
            UUID uuid = UUID.randomUUID();
            threadLocal.set(uuid);
            System.out.println(getName() + "======threadLocal.set = " + uuid);
            //睡一会保证两个线程的set都执行完了,如果下面get都是Thread1的UUID则证明Thread2中的值已经被覆盖掉了
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {

            }
            System.out.println(getName() + "======threadLocal.get() = " + threadLocal.get());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread myThreadA = new MyThread();
        myThreadA.setName("Thread1");
        MyThread myThreadB = new MyThread();
        myThreadB.setName("Thread2");
        myThreadA.start();
        myThreadB.start();
    }

######################打印结果##################
Thread2======threadLocal.set = ea605de0-9073-44d0-9813-eab8877fe2f4
Thread1======threadLocal.set = 646da0bd-b21c-4525-925c-e76385a32417
Thread2======threadLocal.get() = ea605de0-9073-44d0-9813-eab8877fe2f4
Thread1======threadLocal.get() = 646da0bd-b21c-4525-925c-e76385a32417

从结果上看,每个线程都能取出自己设置的数据,确实可以达到隔离线程变量的效果。

三.源码分析

 public T get() {
        Thread t = Thread.currentThread();
        /**
         * 获取当前线程中的ThreadLocalMap
         * 这个ThreadLocalMap是当前线程Thread内部维护的,他是ThreadLocal保存数据幕后的人
         * ThreadLocalMap getMap(Thread t) {
         *
         *        return t.threadLocals;
         *  }
         */
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            /**
             * 根据当前ThreadLocal实例获取保存在 private Entry[] table中的Entry
             *  private Entry getEntry(ThreadLocal<?> key) {
             *             int i = key.threadLocalHashCode & (table.length - 1);
             *             Entry e = table[i];
             *             if (e != null && e.get() == key)
             *                 return e;
             *             else
             *                 return getEntryAfterMiss(key, i, e);
             *         }
             *
             *
             * static class Entry extends WeakReference<ThreadLocal<?>> {
             *                 Object value;
             *                 这里用了弱引用(弱引用:仅有弱引用指向的对象,只要发生gc就会被回收。)
             *                 保存了key即ThreadLocal
             *       
             * 
             *                 Entry(ThreadLocal<?> k, Object v) {
             *                     super(k);
             *                     value = v;
             *                 }
             *             }
             */
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    //设置变量的值
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
           //保存value
            map.set(this, value);
        else
            createMap(t, value);
    }

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            //初始化ThreadLocalMap
            createMap(t, value);
        return value;
    }
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    //删除当前线程中ThreadLocalMap对应的ThreadLocal
    public void remove() {
        ThreadLocalMap m = getMap(Thread.currentThread());
        if (m != null)
            m.remove(this);
    }

相互的关系可以用如下关系图进行表示,实现表示强引用,虚线表示弱引用
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值