ThreadLocal及InheritableThreadLocal类实现原理

Thread使用方法

ThreadLocal提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程其实都会有此变量的一个本地副本。所以多线程访问次变量实际操作的时自己线程本地内存里面的变量。直接上例子:

static ThreadLocal<String> localVariable = new ThreadLocal<>();

public static void main(String[] args) {
       Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                localVariable.set("I'm variable in threadOne");
                System.out.println("threadOne variable:" + localVariable.get());
            }
        });
        Thread threadTwo = new Thread(new Runnable() {
            @Override
            public void run() {
                localVariable.set("I'm variable in threadTwo");
                System.out.println("threadTwo variable:" + localVariable.get());
            }
        });
        threadOne.start();
        threadTwo.start();
    }

执行结果为如下。说明虽然程序里面是操作的同一个变量localVariable,但是不同线程都有自己的一份拷贝。

threadOne variable:I'm variable in threadOne
threadTwo variable:I'm variable in threadTwo

ThreadLocal实现原理

如图所示为ThreadLocal相关类的类图结构。
ThreadLocal及InheritableThreadLocal类实现原理_第1张图片
从该图中能够看到,Thread类中有一个threadLocals和一个inheritableThreadLocals变量,inheritableThreadLocals和继承有关,本文主要先讲threadLocals实现。他们都是ThreadLocalMap类型。ThreadLocalMap是一个定制化的HashMap,默认情况下,这两个变量都为空,只有第一次调用set或get方法时才会创建他们。能够看出来,其实线程的本地变量没有存在ThreadLocal里面,而是存在了自己的线程内部,只是通过ThreadLocal来访问它们,下面看各方法实现原理的时候能够认清这一点。ThreadLocal被设置成HashMap也是因为一个线程可以存放多个本地变量。

void set(value: T)方法

其实现代码如下:

void set(T value) {
	Thread t = Thread.currentThread();
	ThreadLocalMap map = getMap(T);		//根据当前线程获得它自己的map变量
	if (map != null)
		map.set(this, value);		//以此ThreadLocal变量为key,放入到此线程的value中
	else
		createMap(t, value);	//创建新的ThreadLocalMap
}

代码的逻辑也很清楚,要注意的是,每个线程有一个threadLocals变量,其中每个键值对的键就是某一个ThreadLocal实例对象引用,值就是所对应的变量。例如上面提到的例子中,threadOne的threadLocals变量中就存在这样一个键值对:


createMap的实现为:

void createMap(Thread T, T firstValue) {
	t.threadLocals = new ThreadLocalMap(this, firstValue);		//新创建一个ThreadLocalMap并将键值对放进去
}

T get()方法

其实现代码如下:

public T get() {
	Thread t = Thread.currentThread();
	ThreadLocalMap map = getMap(t);
	if (map != null) {
		ThreadLocalMap.Entry e = map.getEntry(this);		//使用当前ThreadLocal变量作为key,取得这个Entry
		if (e != null) {
			@SuppressWarnings("unchecked")
			T result = (T)e.value;
			return result;
		}
	}
	return setInitialValue();	//threadLocalMaps为空,则初始化当前线程的threadLocals成员变量
}

setInitialValue()方法的实现如下:

private T setInitialValue() {
    T value = initialValue();		//初始化值
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    	if (map != null)			//再一次判断当前Map是不是空
    		map.set(this, value);
    	else
    		createMap(t, value);		//本质还是调用了createMap函数
    return value;
}

protected T initialValue() {
	return null;
}

void remove()方法

实现代码如下:

public void remove() {
	ThreadLocalMap m = getMap(Thread.currentThread());
	if (m != null)
		m.remove(this);		//从map中将当前线程为键的键值对删掉
}

总结

每个线程内部都有一个名为threadLocals的成员变量,该变量类型为HashMap,key为我们定义的ThreadLocal变量的this引用,value则为我们使用set方法设置的值。如图所示:
ThreadLocal及InheritableThreadLocal类实现原理_第2张图片

InheritableThreadLocal类

ThreadLocal是不支持继承性的,所谓继承性也是针对父线程和子线程来说的,例如:

public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
 public static void main(String[] args) {
        threadLocal.set("hello world");
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread:" + threadLocal.get());
            }
        });
        thread.start();

        System.out.println("main:" + threadLocal.get());
    }

输出结果如下:能够理解,即使thread是main线程的子线程,但仍然是两个不同的线程,所以各自的threadLocal的值是不一样的。那为了子线程能够直接访问父线程的变量,InheritableThreadLocal类就产生了。

thread:null
main:hello world

InheritableThreadLocal提供了一个特性,就是子线程可以访问在父线程中设置的本地变量,先看一下InheritableThreadLocal的代码:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
	(1)
    protected T childValue(T parentValue) {
        return parentValue;
    }
	(2)
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    (3)
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

能看到代码(2)和代码(3)变成了针对于线程中的inheritableThreadLocals变量进行操作的,而不再是threadLocals变量,而本身inheritableThreadLocals也是一个ThreadLocal类型变量。代码(1)有什么用呢?先看一下Thread在创建时发生了什么,即Thread类的默认构造函数:

public Thread(Runnable target) {
	init(null, target, "Thread-" + nextThreadNum(), 0);
}

private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) {
	//获取“当前”线程,也就是创建此线程的线程,或者说叫父线程
	Thread parent = currentThread();
	//如果父线程的inheritableThreadLocals变量不为null
	if (parent.inheritableLocals != null) {
		//利用父线程中的inheritableThreadLocals来设置子线程中的inheritableThreadLocals变量
		this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
		this.stackSize = stackSize;
		tid = nextThreadID();
	}
}

createInheritedMap代码如下,即利用父线程的inheritableThreadLocals变量作为ThreadLocalMap构造函数的参数构造一个新的ThreadLocalMap变量,并返回给子线程(当前线程)的inheritableThreadLocals变量。

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}

构造函数代码及所做的工作如下:将父线程inheritableThreadLocals变量中所有的Entry都复制到子线程的inheritableThreadLocals(此处存放到了table中变量中)中。此处用到了代码(1)重写的部分。

        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            //存放复制的结果
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    //得到父线程中变量对应的key
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                    	//重写代码(1)的方法
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

其实从代码(1)本身的定义能够看到,它基本没什么作用,只是形式上利用父线程中的value来获取子线程中的value,但是两者是一个,就是将参数里面的值直接返回,我想在此处要重写成这样,可能是为了以后改变的策略的时候只需要改变这一处就行。比如不想返回和父线程一样的value,而是要加某些操作,那只需修改这个函数,所有其他部分都会修改。

你可能感兴趣的:(java多线程)