4.2线程局部变量(Thread-Local Variables)

  • Post author:
  • Post category:其他


你有时候想要通过每个线程的数据(如一个用户的ID)连接一个线程。尽管你可以使用局部变量完成这个任务,仅仅只有局部变量存在时你才可以这样做。你可以用一个实例的字段去长久保存这个数据,但是你需要去处理同步。幸好,Java提供了java.lang.ThreadLocal类作为一个简的替换使用。

每个ThreadLocal实例是一个线程局部变量(Thread-Local Variables),这个局部变量为每个访问变量的线程提供单独的存储槽。你可以认为一个线程局部变量是一个多槽的变量,在它的每个线程里可以存储相同变量的不同值。每个线程只关注自己的值,和不关注其它线程它们自己的值在这变量中。

ThreadLocal的声明是ThreadLocal<T>,这个T是指示值的类型用于存储在这个变量中。这个类声明了如下的构造器和方法:

(1)ThreadLocal:创建一个新的线程局部变量。

(2) T get():在请求线程存储槽中返回值。当线程请求这个方法,而线程存储槽不存在,那么get()会去请求initialValue()方法。

(3)T initialValue():创建请求线程存储槽,和存储默认值在这个存储槽。这个初始值是null。你必须创建ThreadLocal的子类,并重写它的protected方法去提供更多有效的值。

(4)void remove():移除请求线程存储槽。如果这个方法调用get()却没使用set()。那么这个get()会调用initialValue()方法。

void set(T value):将值放入到请求线程存储槽。

Listing4-3展示了如何使用ThreadLocal去连接两个线程的不同用户IDs

package com.owen.thread.chapter4;

public class ThreadLocalDemo
{

	private static volatile ThreadLocal<String> userID = new ThreadLocal<String>();

	public static void main(String[] args)
	{
		Runnable r = new Runnable()
		{
			@Override
			public void run()
			{
				String name = Thread.currentThread().getName();
				if (name.equals("A"))
					userID.set("foxtrot");
				else
					userID.set("charlie");
				System.out.println(name + " " + userID.get());
			}
		};
		Thread thdA = new Thread(r);
		thdA.setName("A");
		Thread thdB = new Thread(r);
		thdB.setName("B");
		thdA.start();
		thdB.start();
	}
}

之后实例化ThreadLocal和定义一个引用volatile的全局类命名为userID(这个全局是一个volatile因为它需要通过不同的线程,它可能在多处理器或多核机器上运行——用它代替final),默认主线程创建两个或多线程,它存储不同的java.lang.String.object在userID中,和输出它们的对象。

执行上面的代码,你将会看到:

A foxtrot
B charlie

值存储在线程局部变量中,而这些值之间没有联系。当一个新的线程被创建,它获取一个新的存储槽包含initialValue()的值。可能你需要涉及来自一个父线程(a parent thread)的值,一个线程创建另一个线程,给一个子线程,这个线程是创建过的。你可以用InheritableThreadLocal来完成任务。

InheritableThreadLocal是ThreadLocal的一个子类。下面也就是我们所知道的InheritableThreadLocal的构造器:

(1)T childValue(T parentValue):计算子类的初始值作为一个父值的应用,这个时候子线程被创建。在子线程被创建之前这个方法从父线程中请求。这个方法从parentValue中返回参数,和当其它值被要求时这个方法需要重写。

Listing4-4展示如何运用InheritableThreadLocal通过父线程的Integer对象给子线程。

Listing4-4通过父线程的对象给一个子线程。

package com.owen.thread.chapter4;

public class InheritableThreadLocalDemo
{

	private static final InheritableThreadLocal<Integer> intVal = new InheritableThreadLocal<Integer>();

	public static void main(String[] args)
	{
		Runnable rP = () -> {
			intVal.set(new Integer(10));
			Runnable rC = () -> {
				Thread thd = Thread.currentThread();
				String name = thd.getName();
				System.out.printf("%s %d%n", name, intVal.get());
			};
			Thread thdChild = new Thread(rC);
			thdChild.setName("Child");
			thdChild.start();
		};
		new Thread(rP).start();
	}

}

之后初始化InheritableThreadLocal和定义它为final类的变量(也可以用volatile替代)命名为intVal,这个默认线程创建一个父线程,它存储一个java.lang.Integer对象包含值为10在intVal变量中。这个父线程创建一个子线程,它调用intVal和接收它的父线程的Integer对象。执行上面代码你将会看到:Child 10。


源码下载:git@github.com:owenwilliam/Thread.git