我们知道在线程间共享变量会存在风险,即线程安全问题,解决线程安全问题的办法有很多种,之前讲到的同步机制就是一种。
其实我们也可以通过使用ThreadLocal辅助类为各个线程提供各自的实例,避免共享变量。
同步机制是采用“以时间换空间”,而线程局部变量则是采用“以空间换时间”。
因为同步机制是将线程的执行由“并行执行”转为串行执行,没有获得锁的线程需要等待获得锁的线程释放锁,增加了时间上的开销;线程局部变量由于每个线程都有变量的副本,对自己变量副本的修改不会影响其他线程,所以可以独立执行,不需要考虑到其他线程,时间上的开销减少了,但是每个线程都拥有变量的副本,这无疑又增加了空间上的开销。
使用方法
ThreadLocal不是一个线程,只是线程的一个本地化对象,ThreadLocal实例通常是类中的private static字段。
要为每个线程构造一个实例,可以使用以下代码:
public static ThreadLocal<Integer> ThreadNum = new ThreadLocal<Integer>() {
protected Integer initialValue() {
return 0;
}
};
以上代码是采用匿名内部类的方式构造了一个ThreadLocal类,并且我们应该覆盖initialValue方法来提供一个初始值,若不重写此方法,则,默认返回null。
ThreadLocal类除了initialValue方法外,还有set、get和remove方法。
下面我们写一个完整的程序来体会一下ThreadLocal的使用:
public class ThreadLocalTest {
public static void main(String[] args) {
Thread MyThread1 = new Thread(new MyThread(), "MyThread1");
Thread MyThread2 = new Thread(new MyThread(), "MyThread2");
MyThread1.start();
MyThread2.start();
}
}
class MyThread implements Runnable {
public static ThreadLocal<Integer> ThreadNum = new ThreadLocal<Integer>() {
protected Integer initialValue() {
return 0;
}
};
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 10; i++) {
ThreadNum.set(i);
System.out.println(Thread.currentThread().getName() + "->" + ThreadNum.get());
}
}
}
运行结果:
可以看到,Thread1和Thread2两个线程都各自打印了0~9十个数字,没有互相干扰。
源码分析
ThreadLocal是一个以ThreadLocal对象为键、任意对象为值的存储结构,并且是根据特定的线程去取值。
在ThreadLocal类中有一个静态内部类ThreadLocalMap,它可以理解为就是一个Map,存储以ThreadLocal对象为key,线程局部变量为值的value的Entry对象。
再看一下get方法的源码,我们会发现,其中的逻辑是先获取到当前的线程对象,再通过当前线程对象来获得他的ThreadLocalMap对象。如果Map不为空,则以当前ThreadLocal对象为key去获取Entry中对应的value值。如果Map为空,则会调用setInitialValue()方法,将初始值存入Map中。