5

如果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声明应该是静态的?

4

3 回答 3

8

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

AH 的回答非常好,我会建议对其进行进一步的修改。看起来您可能希望在调用代码中而不是在地图的定义中控制日期格式。您可以使用以下代码来做到这一点:

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();
    }
}

您在哪里懒惰地创建ThreadLocals。

于 2012-03-11T12:49:31.417 回答
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在某些线程中未使用的 s。
于 2012-03-11T12:48:04.260 回答
1

通过使用静态 ThreadLocal<Map<String, DateFormat>>,在 ThreadLocal 中缓存模式可能会更有效。而不是您所描述的其他方式。

尽管如果您确实需要将 ThreadLocals 用作实例变量(有些情况),请考虑以下事项:

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

我在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

于 2021-04-02T21:18:06.247 回答