Java中ThreadLocal使用及分析

0. ThreadLocal简单使用
  • ThreadLocal类的简单使用。下面(程序中)共有 10 个 ‘人’ 同时命令 ‘小李子’ 做点什么事情 : - ),代码如下(使用 ThreadLocal 完成统计方法的执行时间):
import java.util.Random;
import java.util.concurrent.TimeUnit;

public class Temp_4 {
	public static void main(String[] args) throws InterruptedException {
		Person person = new Person("小李子");
		System.out.println(person.getName() + " begin to do");
		int threadNum = 10;
		Thread[] workers = new Thread[threadNum];
		for(int i = 0;i < threadNum;i++) {
			workers[i] = new Thread(new Runnable() {
				@Override
				public void run() {
					person.toDoSomething();
				}
			}, "t-" + i);
		}
		for(int i = 0;i < threadNum;i++) {
			workers[i].start();
		}
		for(int i = 0;i < threadNum;i++) {
			workers[i].join();
		}
		System.out.println("all works done.");
	}
}

class Person {
	private static ThreadLocal<Long> costTime = new ThreadLocal<Long>();
	private static Random random = new Random();
	
	private final String name;
	public Person(String name) {
		this.name = name;
	}
	public String getName() {
		return name;
	}
	public void toDoSomething() {
		costTime.set(System.currentTimeMillis()); // 上班打卡
		String threadName = Thread.currentThread().getName();
		int sleepTime = random.nextInt(1001);
		while(sleepTime == 0) {			
			sleepTime = random.nextInt(1001);
		}
		try {
			TimeUnit.MILLISECONDS.sleep(sleepTime); // 小李子只想睡觉
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		// 打卡下班
		System.out.println(threadName + " command " + name 
				+ " to do something, cost time is " 
				+ (System.currentTimeMillis() - costTime.get()) / 1000.0 + "s");
	}
}

执行结果如下:

小李子 begin to do
t-1 command 小李子 to do something, cost time is 0.083s
t-2 command 小李子 to do something, cost time is 0.193s
t-6 command 小李子 to do something, cost time is 0.396s
t-9 command 小李子 to do something, cost time is 0.41s
t-7 command 小李子 to do something, cost time is 0.461s
t-5 command 小李子 to do something, cost time is 0.488s
t-3 command 小李子 to do something, cost time is 0.494s
t-8 command 小李子 to do something, cost time is 0.575s
t-4 command 小李子 to do something, cost time is 0.668s
t-0 command 小李子 to do something, cost time is 0.803s
all works done.

查看程序的运行结果,从中我并没有看出什么(-_- !),所以还是去看一眼 ThreadLocal 类的具体实现。(猜想:我们知道,ThreadLocal 可以实现:为每个线程保存一个对象,所以我猜测它的内部必定有一个数据结构/容器来维护每个线程及其持有的对象,该数据结构/容器要保证多线程安全,而且当线程终止后,ThreadLocal 最好还能够释放对该线程及其所持对象的引用)

1. ThreadLocal 类的具体实现
  • 从源代码开始。ThreadLocal 对外暴露的可供我们使用的方法总共只有 3 个:
    Java中ThreadLocal使用及分析_第1张图片
    该类仅仅只有一个无参的构造器方法,且该方法的方法体为空。
    先看 get 方法,该方法返回 ThreadLocal 对象实例关联的 value 值(这里使用泛型,所以我们可以为线程保存任意的对象实例),get 方法源代码如下:
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();
    }

其中 ThreadLocalMap 为一个自定义的 hashmap 实现(暂时不要关注它的实现细节),其目的是存放键值对 即每个 ThreadLocal 实例关联一个值 value,且线程类 Thread 中声明了一个 ThreadLocalMap 类型属性,用于保存属于该线程的 ‘线程本地变量’。我们知道,对于任一线程,一个 ThreadLocal 对象只关联一个值,而一个线程可以拥有多个 ThreadLocal 实例,所以就可以清晰:每个线程都有一个 hashmap 即 ThreadLocalMap 实例,用来保存属于该线程的 ThreadLocal 实例以及与该 ThreadLocal 实例对应的值 value。注意,每个线程都在一个属于它自己的 hashmap 对象上进行( get/set)操作,所以这里并不会有线程安全问题。当多个线程使用/操作同一个 ThreadLocal 实例时,相当于:有多个 hashmap 实例,使用同一个对象(即 ThreadLocal 实例)作为 key,来关联它们各自的 value 值。
综上,我是为了了解 ThreadLocal 的实现机制,所以从 ThreadLocal 类本身开始下手,但是直到看 ThreadLocalMap 以及线程类 Thread 中的 ThreadLocalMap 类型的属性域,我才恍然大悟:我搞错了顺序,我一开始就进入到了最里面,从而困惑于 ThreadLocal 的实现细节。我认为对 ThreadLocal 的理解,应当从 Thread 类中的 ThreadLocalMap 类型的属性开始:每个线程都有个一 ThreadLocalMap 类型的 hashmap 域,用于维护以 ThreadLocal 对象为 key 并以任意对象为 value 的键值对。所以再看 ThreadLocal,其仅仅用于创建一个普通的对象,该对象没有状态,只是作为 hashmap 的 key 而存在,至于它被多个线程使用,这一点并不特别。(还有一点就是 ThreadLocal 类本身,我认为这个类被设计的比较复杂,因为该类内部还隐藏了其它 3 个类,最关键的就是 ThreadLocalMap 类了,ThreadLocalMap 以它的包装类 ThreadLocal 作为 key,其自身却藏在 ThreadLocal 里面,可见类设计者有意隐藏 ThreadLocal 的实现细节,使得可以像使用普通对象一样对待 ThreadLocal 实例。注意,ThreadLocalMap 虽然定义在 ThreadLocal 的内部,但是它被定义为内部静态类,这和单独定义它其实效果一样,这一点可以简化对 ThreadLocal 的理解。)

你可能感兴趣的:(Java)