Использование ThreadLocal в переменных экземпляра - PullRequest
5 голосов
/ 11 марта 2012

Делают ли переменные 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 объектов, хранящихся на карте.

Имеет ли какой-либо из вышеперечисленных подходов смысл для описанного случая, или объявления ThreadLocal должны быть статическими?

Ответы [ 2 ]

8 голосов
/ 11 марта 2012

Чтобы ответить на ваш главный вопрос, ThreadLocal предоставляет каждому потоку отдельное значение этого ThreadLocal экземпляра . Таким образом, если у вас есть два экземпляра в разных местах, каждый поток будет иметь отдельные значения в каждом. Вот почему ThreadLocal s так часто статичны; если все, что вам нужно, это отдельное значение для переменной в потоке, то вам нужна только одна ThreadLocal для этой переменной в JVM.

Ответ А.Х. очень хороший, и я предложу еще один вариант. Похоже, вы захотите поставить контроль над форматами даты в коде вызова, а не в определении карты. Вы могли бы сделать это с кодом что-то вроде:

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.

7 голосов
/ 11 марта 2012

Производят ли переменные 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, которые не используются в некоторых потоках.
...