ThreadLocal源码分析

场景

假设在多个线程共用同一个类里面的属性,这里的共用指的是读和写操作,如下代码所示。

public class ThreadLocalTest {

    private String string;

    public String getString() {
        return string;
    }

    public void setString(String string) {
        this.string = string;
    }

    public static void main(String[] args) {
        int threads = 9;
        ThreadLocalTest threadLocalTest = new ThreadLocalTest();
        for (int i = 0; i < threads; i++) {
            Thread thread = new Thread(() -> {
                threadLocalTest.setString(Thread.currentThread().getName());
                System.out.println(threadLocalTest.getString());
            }, "Thread - " + i);
            thread.start();
        }

    }
}

输出结果:

Thread - 0
Thread - 0
Thread - 3
Thread - 4
Thread - 7
Thread - 8
Thread - 2
Thread - 5
Thread - 6

出现两个0的原因是因为当1线程执行set方法的时候(还没有进行输出),此时0线程执行了set方法,原来写进去的1被0覆盖掉了。

使用ThreadLocal

public class ThreadLocalTest {

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

    public String getString() {
        return threadLocal.get();
    }

    public void setString(String string) {
        threadLocal.set(string);
    }

    public static void main(String[] args) {
        int threads = 9;
        ThreadLocalTest threadLocalTest = new ThreadLocalTest();
        for (int i = 0; i < threads; i++) {
            Thread thread = new Thread(() -> {
                threadLocalTest.setString(Thread.currentThread().getName());
                System.out.println(threadLocalTest.getString());
            }, "Thread - " + i);
            thread.start();
        }

    }
}

结果:

Thread - 1
Thread - 0
Thread - 6
Thread - 7
Thread - 3
Thread - 2
Thread - 4
Thread - 5
Thread - 8

ThreadLocal是什么

ThreadLocal 是 JDK java.lang 包中的一个用来实现相同线程数据共享不同的线程数据隔离的一个工具。 我们来看下 JDK 源码中是如何解释的:

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 变量的副本。当一个线程结束时,它所持有的所有 ThreadLocal 相对的实例副本都可被回收。

通俗点解释:每个线程在ThreadLocal里面set的变量都是独立的。当该线程结束,里面独立的变量就可以被垃圾回收

源码分析

get方法
public T get() {
	//获取当前线程
	Thread t = Thread.currentThread();
	//调用getMap(t)方法,将当前线程作为参数,返回一个ThreadLocalMap 
	ThreadLocalMap map = getMap(t);
	//如果map不为空则调用getEntry返回一个Entry,返回Entry的value,为空调用setInitialValue()方法
	if (map != null) {
		ThreadLocalMap.Entry e = map.getEntry(this);
		if (e != null) {
			@SuppressWarnings("unchecked")
			T result = (T)e.value;
			return result;
		}
	}
	return setInitialValue();
}

再来看getMap方法

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

返回的是当前线程t的一个成员变量threadLocals,继续看成员变量threadLocals

ThreadLocal.ThreadLocalMap threadLocals = null;

实际上是ThreadLocalMap,这个类是ThreadLocal的一个内部类,从中拿出来几个比较关键的属性和 get方法中的 getEntry()方法

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 Entry[] table;
	
	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);
	}
	
}

简单分析一下,ThreadLocal内部有一个Entry,Entry继承了WeakReference,同时ThreadLocal的table属性是一个Entry类型的数组。

getEntry(ThreadLocal<?> key)这个方法通过threadLocalHashCode和数组长度-1的与运算计算出来下标i,从数组中获取i下标的值,判断这个值是不是当前线程所属的,如果是则返回e。

setInitialValue方法

在get方法中可以看到,如果getMap返回的是空,则执行setInitialValue方法。

private T setInitialValue() {
	T value = initialValue();
	//获取当前线程
	Thread t = Thread.currentThread();
	//因为我们就是从map == null过来的,所以map必然是null,走else分支
	ThreadLocalMap map = getMap(t);
	if (map != null)
		map.set(this, value);
	else
		createMap(t, value);
	return value;
}

再看下createMap方法

void createMap(Thread t, T firstValue) {
	t.threadLocals = new ThreadLocalMap(this, firstValue);
}

这个方法比较好理解,如果ThreadLocalMap为空,那么就初始化一个ThreadLocalMap,key为当前线程,value是T类型变量。

set方法
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

与setInitialValue方法相比就差了一行代码。

源码分析总结

每个线程在ThreadLocal里面都有一个ThreadLocalMap类型的成员变量——threadLocals,这个threadLocals就是用来存储实际的变量副本,键为当前线程,value值为T类型的变量,

初始时,ThreadLocal里的threadLocals为空,当通过ThreadLocal变量调用get()或者set()方法的时候,就会对threadLocals进行初始化,并以当前线程作为键,以ThreadLocal要保存的副本变量为value,存到threadLocals中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值