从浅到深Java线程之ThreadLocal

由浅入深Java线程之ThreadLocal
  还记得Java并发最佳实践有一条提到尽量不要在线程间共享状态。但我们在实现一个thread或者runnable接口的时候很容易放这个错误,导致一些诡异的问题。
  让我们看下面这个例子:
public class UnsafeTask implements Runnable {

	private Date startDate;

	@Override
	public void run() {
		startDate = new Date();
		System.out.printf("Starting Thread: %s : %s\n", Thread.currentThread()
				.getId(), startDate);
		try {
			TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.printf("Thread Finished: %s : %s\n", Thread.currentThread()
				.getId(), startDate);
	}

}

public class Core {
	public static void main(String[] args) {
		UnsafeTask task = new UnsafeTask();
		for (int i = 0; i < 10; i++) {
			Thread thread = new Thread(task);
			thread.start();
			try {
				TimeUnit.SECONDS.sleep(2);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}


  我们看到如下输出:
Starting Thread: 9 : Thu Feb 27 17:26:34 CST 2014
Starting Thread: 10 : Thu Feb 27 17:26:36 CST 2014
Starting Thread: 11 : Thu Feb 27 17:26:38 CST 2014
Starting Thread: 12 : Thu Feb 27 17:26:40 CST 2014
Thread Finished: 11 : Thu Feb 27 17:26:40 CST 2014

  结束的线程显示的日期与刚启动的线程的日期是一样的,原因处在我们在同一个Runnable实例上启动了多个线程,而startDate域是多个线程之间共享的。
  怎样避免这个问题呢? 一种方法是让一个线程对应一个Runnable实例,还有一种更有效的方法就是用Java Concurrency API提供的ThreadLocal变量,这种方法可以避免创建过多的Runnable实例。
  看如下代码:
 
import java.util.Date;
import java.util.concurrent.TimeUnit;

public class SafeTask implements Runnable {

	private static ThreadLocal<Date> startDate = new ThreadLocal<Date>() {
		protected Date initialValue(){
			return new Date();
		}
	};
	
	@Override
	public void run() {
		System.out.printf("Starting Thread: %s : %s\n",Thread.
				currentThread().getId(),startDate.get());
				try {
				TimeUnit.SECONDS.sleep((int)Math.rint(Math.random()*10));
				} catch (InterruptedException e) {
				e.printStackTrace();
				}
				System.out.printf("Thread Finished: %s : %s\n",Thread.
				currentThread().getId(),startDate.get());
	}

}

  仔细查看源代码,ThreadLocal实现中有一个TheadLocalMap(开地址哈希?),存放各个Thread和值对应的值域,map的key是用线程和它的域的组合算出来的,这样每个线程就不共享状态了。
  初次之外,还可以调用ThreadLocal的get(),set()方法去获得,更新自己的状态。
  JDK还提供了一个更复杂的InheritableThreadLocal类,如果A线程创建了B线程,给类可以帮助B从A中获取相关状态域的一份拷贝。