ThreadLocal实现原理

一、概述
ThreadLocal官网解释:

  This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable.  {@code 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类用来提供线程内部的局部变量。这些变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量,ThreadLocal实例通常来说都是private static类型。
总结:ThreadLocal不是为了解决多线程访问共享变量,而是为每个线程创建一个单独的变量副本,提供了保持对象的方法和避免参数传递的复杂性。

ThreadLocal的主要应用场景为按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。例如:同一个网站登录用户,每个用户服务器会为其开一个线程,每个线程中创建一个ThreadLocal,里面存用户基本信息等,在很多页面跳转时,会显示用户信息或者得到用户的一些信息等频繁操作,这样多线程之间并没有联系而且当前线程也可以及时获取想要的数据。


二、实现原理

ThreadLocal可以看做是一个容器,容器里面存放着属于当前线程的变量。ThreadLocal类提供了四个对外开放的接口方法,这也是用户操作ThreadLocal类的基本方法:
(1) void set(Object value)设置当前线程的线程局部变量的值。
(2) public Object get()该方法返回当前线程所对应的线程局部变量。
(3) public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
(4) protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次,ThreadLocal中的缺省实现直接返回一个null。

可以通过上述的几个方法实现ThreadLocal中变量的访问,数据设置,初始化以及删除局部变量,那ThreadLocal内部是如何为每一个线程维护变量副本的呢?

其实在ThreadLocal类中有一个静态内部类ThreadLocalMap(其类似于Map),用键值对的形式存储每一个线程的变量副本,ThreadLocalMap中元素的key为当前ThreadLocal对象,而value对应线程的变量副本,每个线程可能存在多个ThreadLocal。

源代码:

/**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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();
    }

    /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    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;
    }

    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    /**
     * Removes the current thread's value for this thread-local
     * variable.  If this thread-local variable is subsequently
     * {@linkplain #get read} by the current thread, its value will be
     * reinitialized by invoking its {@link #initialValue} method,
     * unless its value is {@linkplain #set set} by the current thread
     * in the interim.  This may result in multiple invocations of the
     * {@code initialValue} method in the current thread.
     *
     * @since 1.5
     */
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

上述是在ThreadLocal类中的几个主要的方法,他们的核心都是对其内部类ThreadLocalMap进行操作,下面看一下该类的源代码:

static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

        //....其他的方法和操作都和map的类似
}

总之,为不同线程创建不同的ThreadLocalMap,用线程本身为区分点,每个线程之间其实没有任何的联系,说是说存放了变量的副本,其实可以理解为为每个线程单独new了一个对象。

 

二、范例解析

假设三个人想从镜子中看自己,第一个方案就是每人发一个镜子互不干扰,第二个方案就是只有一个镜子,一个人站在镜子前其他人要排队等候,第三个方案就是我这里发明了一种“魔镜”,所有人站在镜子前可以并且只能看到自己!!!

        public static void main(String[] args) {
		//Mirror是个单例的,只构建了一个对象
		Mirror mirror = new Mirror("魔镜");
		
		//三个线程都在用这面镜子
		MirrorThread thread1 = new MirrorThread(mirror,"张三");
		MirrorThread thread2 = new MirrorThread(mirror,"李四");
		MirrorThread thread3 = new MirrorThread(mirror,"王二");
		
		thread1.start();
		thread2.start();
		thread3.start();
	}

很好理解,创建了一面镜子,3个人一起照镜子。

MirrorThread:

public class MirrorThread extends Thread{
	private Mirror mirror;
	
	private String threadName;
	
	public MirrorThread(Mirror mirror, String threadName){
		this.mirror = mirror;
		this.threadName = threadName;
	}
		
	//照镜子
	public String lookMirror() {
		return threadName+" looks like "+ mirror.getNowLookLike().get();
	}
	
	//化妆
	public void makeup(String makeupString) {
		mirror.getNowLookLike().set(makeupString);
	}
	
	@Override
        public void run() {
		int i = 1;//阈值
		while(i<5) {
			try {
				long nowFace = (long)(Math.random()*5000);
				sleep(nowFace);
				StringBuffer sb = new StringBuffer();
				sb.append("第"+i+"轮从");
				sb.append(lookMirror());
				makeup(String.valueOf(nowFace));
				sb.append("变为");
				sb.append(lookMirror());
				System.out.println(sb);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			i++;
		}
	}
}

这个类也很好理解,就是不断的更新自己的外貌同时从镜子里读取自己的外貌。

重点是Mirror:

public class Mirror {
	private String mirrorName;
	
	//每个人要看到自己的样子,所以这里要用ThreadLocal
	private ThreadLocal<String> nowLookLike;
	
	public Mirror(String mirrorName){
		this.mirrorName=mirrorName;
		nowLookLike = new ThreadLocal<String>();
	}
 
	public String getMirrorName() {
		return mirrorName;
	}
 
	public ThreadLocal<String> getNowLookLike() {
		return nowLookLike;
	}
}

对每个人长的样子用ThreadLocal类型来表示。

先看测试结果:

第1轮从张三 looks like null变为张三 looks like 3008
第2轮从张三 looks like 3008变为张三 looks like 490
第1轮从王二 looks like null变为王二 looks like 3982
第1轮从李四 looks like null变为李四 looks like 4390
第2轮从王二 looks like 3982变为王二 looks like 1415
第2轮从李四 looks like 4390变为李四 looks like 1255
第3轮从王二 looks like 1415变为王二 looks like 758
第3轮从张三 looks like 490变为张三 looks like 2746
第3轮从李四 looks like 1255变为李四 looks like 845
第4轮从李四 looks like 845变为李四 looks like 1123
第4轮从张三 looks like 2746变为张三 looks like 2126
第4轮从王二 looks like 758变为王二 looks like 4516

ThreadLocal应用其实很多,例如Spring容器中实例默认是单例的,transactionManager也一样,那么事务在处理时单例的manager是如何控制每个线程的事务要如何处理呢,这里面就应用了大量的ThreadLocal。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值