11.7 ThreadLocal类
当多个线程需要同时访问一个共享变量时,不可避免地产生数据竞争。通常的做法是利用之前介绍的同步机制来解决这个问题。另外一种做法是使用线程局部变量,即java.lang.ThreadLocal类。在不同的线程访问一个ThreadLocal类的对象时,所访问和修改的是每个线程各自独立的对象,相当于这个对象是线程的一个私有对象。一个线程无法访问其他线程内部的私有对象,因此也不存在数据竞争的问题。通过使用ThreadLocal类,可以快速地把一个非线程安全的对象转换成线程安全的对象。
代码清单11-22中给出了ThreadLocal类的使用示例。在ThreadLocal类的对象中保存的是代码清单11-1中IdGenerator类的对象。ThreadLocal类提供了对其中包含的对象进行处理的方法,get和set方法分别用来获取和设置当前线程中包含的对象的值,而remove方法用来删除对象的值。一般的用法是覆写ThreadLocal类中的initialValue方法来提供对象的初始值。如果没有通过set方法来设置对象的值,那么在第一次调用get方法时会通过initialValue方法来获取对象的初始值。代码清单11-22也给出了ThreadLocal类的一般使用方式。创建一个ThreadLocal类的匿名子类并覆写initialValue方法,并把对ThreadLocal类的对象的使用封装在另外一个类中。程序的其他部分使用这个外部类的方法来获取被ThreadLocal类的对象所管理的对象。不同的线程调用ThreadLocalIdGenerator类的getNext方法时,所使用的是这个线程所对应的私有的IdGenerator类的对象,不同线程使用的是不同的对象。
代码清单11-22 ThreadLocal类的使用示例
public class ThreadLocalIdGenerator{
private static final ThreadLocal<IdGenerator>idGenerator=new ThreadLocal<IdGenerator>(){
protected IdGenerator initialValue(){
return new IdGenerator();
}
};
public static int getNext(){
return idGenerator.get().getNext();
}
}
ThreadLocal类的另外一个作用是创建线程唯一的对象。某些对象的创建比较耗时,可以把对象的创建逻辑封装在ThreadLocal类的initialValue方法中。当通过get方法获取时,所有线程都可以得到唯一的一个对象。在同一个线程中运行的代码访问的都是同一个对象。在有些情况下,一个对象在代码中的各个部分都需要用到,传统的做法是把这个对象作为参数在代码之间进行传递。这种方式需要改变方法的类型声明。如果所有使用这个对象的代码都在同一个线程中运行,可以把该对象封装在一个ThreadLocal类的对象中,这样使用起来会非常方便。ThreadLocal类适合用在Web应用的servlet中。一个servlet请求一般由单一的线程来处理。可以在开始处理请求时把某些全局对象保存到ThreadLocal类的对象中,并在后续的处理中使用,这些全局对象包括servlet请求对象、数据库连接和配置信息等。
在一个多线程程序中,如果需要生成随机数,应该使用Java SE 7中新增的java.util.concurrent.ThreadLocalRandom类。ThreadLocalRandom类中的随机数生成器是使用ThreadLocal类来实现的,避免了使用java.util.Random对象可能带来的竞争问题,可以获得更佳的性能。