如果JavaThreadLocal
变量用作实例变量(例如,在生成线程局部对象的方法中),它们是否会产生线程局部值,或者它们必须始终是静态的吗?
作为一个例子,假设一个典型的场景,其中几个,昂贵的初始化一个非线程安全的类的对象,需要在单个静态初始化块中实例化,存储在单个类的静态变量中(例如,在Map
数据结构),从那时起用于许多不同线程的密集处理。
为了实现线程安全,显然必须传递每个静态对象的不同副本。例如,DateFormat
需要跨不同线程安全使用的 Java 对象。
在网上可以找到的许多示例中,该方法似乎是分别声明每个ThreadLocal
变量,在方法中实例化新对象,initialValue()
然后使用该get()
方法检索线程本地实例。
如果要创建数十或数百个这样的对象,每个对象都有自己的初始化参数,那么这种方法不是很有效。例如,许多SimpleDateFormat
对象各自具有不同的日期模式。
如果对象的实例化可以在每次迭代中产生不同值的循环中完成,则在通过正确初始化相应对象创建每个值之后,将需要用于产生线程局部实例的通用方法。
基于上述情况,以下通用静态方法将不起作用,因为每次调用 initialValue() 时都会产生相同的引用:
// Each value is an object initialized prior to calling getLocal(...)
public static final <T> T getLocal(final T value)
{
ThreadLocal<T> local = new ThreadLocal<T>()
{
@Override
protected T initialValue()
{
return value;
}
};
return local.get();
}
相反,需要一种在 initialValue() 中创建新对象的机制。因此,唯一的通用方法可能是使用反射,其模式类似于
private static final <T> T getLocal(
final Constructor<T> constructor, final Object[] initargs)
{
ThreadLocal<T> local = new ThreadLocal<T>()
{
@Override
protected T initialValue()
{
T value = null;
try // Null if the value object cannot be created
{
value = constructor.newInstance(initargs);
}
catch (Exception e)
{
}
return value;
}
};
return local.get();
}
然后,当然,还有一个特定于类型的选项,可以只使用ThreadLocal
循环中的模式来声明每个变量。
例如,在 的情况下DateFormat
,在单个静态初始化块中,可以执行
private static String[] patterns = ... // Get date patterns
private static DateFormat format;
public static Map<String, DateFormat> formats = new HashMap<String, DateFormat>();
static
{
for (final String pattern:patterns)
{
format = new ThreadLocal<DateFormat>()
{
@Override
protected DateFormat initialValue()
{
return new SimpleDateFormat(pattern);
}
}.get();
formats.put(pattern, format);
}
从那时起,formats
映射将被不同的类、跨不同的线程每次读取,以便调用存储在映射中的一个或多个对象的format()
or方法。parse()
DateFormat
上述任何方法是否对所描述的情况有意义,或者ThreadLocal
声明应该是静态的?