java - 在实例变量中使用 ThreadLocal

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

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

举个例子,假设一个典型的场景,其中初始化一个非线程安全的类的多个昂贵的对象需要在单个静态初始化 block 中实例化,存储在单个类的静态变量中(例如,在 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,在一个单一的静态初始化 block 中,我们可以做

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 () 存储在 map 中的一个或多个 DateFormat 对象的方法。

上述任何方法对所描述的情况是否有意义,或者 ThreadLocal 声明是否应该是静态的?

最佳答案

为了回答您的标题问题,ThreadLocal 为每个线程提供了该ThreadLocal 实例的单独值。所以如果你在不同的地方有两个实例,每个线程都会有不同的值。这就是为什么 ThreadLocal 通常是静态的;如果您只需要为每个线程的变量分配一个单独的值,那么您只需要一个 ThreadLocal 用于 JVM 中的该变量。

A.H.'s answer非常好,我会建议进一步的变化。看起来您可能希望在调用代码中而不是在 map 定义中控制日期格式。你可以用类似这样的代码来做到这一点:

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 的地方。

关于java - 在实例变量中使用 ThreadLocal,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9654455/

相关文章:

asp.net - ASP.NET 中的 ThreadLocal 相当于每个请求变量吗?

authentication - spring 安全 jmx 身份验证器

java - 子线程可以修改其父线程的 Threadlocal 变量吗?

java - Android 上的 Phonegap : Why does triggering the backbutton cause a SocketException?

java - 添加元素以设置的方法总是抛出自定义异常

java - 使用 Bouncy CaSTLe 库 c# 解密 pdf.p7m 文件时出现问题

c - C中的linux套接字和多线程

java - 如何使用 Eclipse 来显示 Main?

java - 多线程会导致静态方法的并发问题吗?

c# - 以线程安全的方式写入文件