在实例变量中使用ThreadLocal

如果Java ThreadLocal变量用作实例变量 (例如,在生成线程局部对象的方法中), 那么它们是否会生成线程局部值 ,或者它们是否必须始终是静态的?

作为一个例子,假设一个典型的场景,其中几个非常昂贵的初始化非线程安全的类的对象需要在单个静态初始化块中实例化,存储在单个类的静态变量中(例如,在Map数据结构)从那时起用于许多不同线程的密集处理。

为了实现线程安全,显然必须传递每个静态对象的不同副本。 例如,需要跨不同线程安全使用的Java DateFormat对象。

在Web上可以找到的许多示例中,方法似乎是分别声明每个ThreadLocal变量,在initialValue()方法中实例化新对象,然后使用get()方法来检索线程本地实例。

如果要创建数十个或数百个这样的对象,每个都有自己的初始化参数,这种方法效率不高。 例如,许多SimpleDateFormat对象各有不同的日期模式。

如果对象的实例化可以在循环中完成,该循环在每次迭代中产生不同的值,则在通过适当地初始化相应对象来创建每个值之后,将需要用于产生线程局部实例的通用方法。

基于以上所述,以下通用静态方法不起作用,因为每次调用initialValue()时都会产生相同的引用:

 // Each value is an object initialized prior to calling getLocal(...) public static final  T getLocal(final T value) { ThreadLocal local = new ThreadLocal() { @Override protected T initialValue() { return value; } }; return local.get(); } 

相反,需要一种在initialValue()中创建新对象的机制。 因此,唯一的通用方法可能是使用reflection,类似于

 private static final  T getLocal( final Constructor constructor, final Object[] initargs) { ThreadLocal local = new ThreadLocal() { @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 formats = new HashMap(); static { for (final String pattern:patterns) { format = new ThreadLocal() { @Override protected DateFormat initialValue() { return new SimpleDateFormat(pattern); } }.get(); formats.put(pattern, format); } 

从那时起, formats映射将由不同的类在不同的线程中读取,每次都是为了调用存储在映射中的一个或多个DateFormat对象的format()parse()方法。

上述任何方法是否适用于所描述的情况,或者ThreadLocal声明是否应该是静态的?

如果将Java ThreadLocal变量用作实例变量,它们是否会生成线程局部值。

是的,他们这样做。 想一想: ThreadLocal不是静态的还是非静态的,只有对ThreadLocal引用是静态的。 对象本身看起来总是一样的。

上述任何方法是否适用于所描述的情况,或者ThreadLocal声明是否应该是静态的?

并不是的。

例:

 [DateFormat] format = new ThreadLocal() {...}.get(); formats.put(pattern, format); 

意味着,您始终创建一个新的ThreadLocal ,立即调用get并将结果 (而不是ThreadLocal )放入映射中。 这意味着您既不重用ThreadLocal也不重用格式本身。

所以,据我所知,你可能想要这样的东西:

 public class XXX { private final static Map formatMap = new HashMap(); static { String[] patterns = {"a", "b", "c"}; for(String pattern: patterns){ formatMap.put(pattern, new SimpleDateFormatThreadLocal(pattern)); } } private static class SimpleDateFormatThreadLocal extends ThreadLocal { private final String pattern; public SimpleDateFormatThreadLocal(String pattern) { this.pattern = pattern; } @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat(pattern); } } } 

示例用法如下:

 public void run(){ String s = formatMap.get("a").get().format(new Date()); System.out.println(s); } 

在这里

  • 重用ThreadLocal对象
  • 重用DateFormat对象(当然是每个线程)
  • 避免创建在某些线程中未使用的DateFormat

为了回答您的标题问题, ThreadLocal为每个线程提供ThreadLocal实例的单独值。 因此,如果您在不同的位置有两个实例,则每个线程在每个实例中都有单独的值。 这就是为什么ThreadLocal经常是静态的; 如果您想要的是每个线程的变量的单独值,那么您只需要在JVM中为该变量使用一个ThreadLocal

AH的答案非常好,我会建议进一步修改它。 您可能希望将控件放在调用代码中的日期格式中,而不是放在地图的定义中。 您可以使用以下代码执行此操作:

 public class DateFormatSupplier { private static final Map> localFormatsByPattern = new HashMap>(); public static DateFormat getFormat(final String pattern) { ThreadLocal localFormat; synchronized (localFormatsByPattern) { localFormat = localFormatsByPattern.get(pattern); if (localFormat == null) { localFormat = new ThreadLocal() { @Override protected DateFormat initialValue() { return new SimpleDateFormat(pattern); } }; localFormatsByPattern.put(pattern, localFormat); } } return localFormat.get(); } } 

你懒洋洋地创建ThreadLocal的地方。