在实例变量中使用ThreadLocal

时间:2012-03-11 11:16:59

标签: java thread-safety thread-local thread-local-storage

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

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

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

在网络上可以找到的许多示例中,方法似乎是单独声明每个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()parse()方法DateFormat 1}}存储在地图中的对象。

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

3 个答案:

答案 0 :(得分:8)

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

A.H.'s answer非常好,我会建议进一步修改它。看起来您可能想要控制调用代码中的日期格式,而不是映射的定义。您可以使用以下代码执行此操作:

public class DateFormatSupplier {
    private static final Map<String, ThreadLocal<DateFormat>> localFormatsByPattern = new HashMap<String, ThreadLocal<DateFormat>>();

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

懒惰地创建ThreadLocal的地方。

答案 1 :(得分:7)

  

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

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

  

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

不是。

示例:

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

表示您总是创建新的ThreadLocal,立即致电get并将结果(而不是ThreadLocal )进入地图。这意味着您既不重用ThreadLocal也不重用格式本身。

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

public class XXX {
    private final static Map<String, SimpleDateFormatThreadLocal> formatMap = 
        new HashMap<String, SimpleDateFormatThreadLocal>();

    static {
        String[] patterns = {"a", "b", "c"};
        for(String pattern: patterns){
            formatMap.put(pattern, new SimpleDateFormatThreadLocal(pattern));
        }
    }

    private static class SimpleDateFormatThreadLocal extends ThreadLocal<SimpleDateFormat> {
        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

答案 2 :(得分:1)

使用静态 ThreadLocal> 可以更有效地缓存 Thr​​eadLocal 中的模式。而不是您描述的相反方式。

虽然如果你真的需要使用 ThreadLocals 作为实例变量(有情况),请考虑以下几点:

当您将 ThreadLocals 的实例初始化为非静态变量时,会出现 ThreadLocal 内存泄漏的一个问题。当持有该变量的对象被垃圾回收时,ThreadLocal 的引用保留在线程中。如果随后在某种循环中实例化并使用许多 ThreadLocal,则会导致内存泄漏。

我在 netty 的 FastThreadLocal 上遇到了这个问题(我猜 java ThreadLocal 应该也有同样的问题)。我的解决方案是在 ThreadLocal 中使用弱引用映射值来解决此问题。这允许使用 ThreadLocal 变量作为实例变量,在释放持有对象时可以对其进行垃圾回收。

这里的代码(可以用来代替 ThreadLocals): https://github.com/invesdwin/invesdwin-util/blob/master/invesdwin-util-parent/invesdwin-util/src/main/java/de/invesdwin/util/concurrent/reference/WeakThreadLocalReference.java