Когда и как я должен использовать ThreadLocal переменной?

когда я должен использовать ThreadLocal переменной?

как им пользоваться?

22 ответов


одно возможное (и общее) использование-когда у вас есть какой-то объект, который не является потокобезопасным, но вы хотите избежать синхронизация доступ к объекту (я смотрю на вас, SimpleDateFormat). Вместо этого дайте каждому потоку собственный экземпляр объекта.

например:

public class Foo
{
    // SimpleDateFormat is not thread-safe, so give one to each thread
    private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue()
        {
            return new SimpleDateFormat("yyyyMMdd HHmm");
        }
    };

    public String formatIt(Date date)
    {
        return formatter.get().format(date);
    }
}

документация.


С ThreadLocal - Это ссылка на данные в пределах данного Thread, вы можете в конечном итоге с утечкой classloading при использовании ThreadLocals на серверах приложений, использующих пулы потоков. Вы должны быть очень осторожны в очистке любого ThreadLocals ты get() или set() С помощью ThreadLocal ' s remove() метод.

если вы не очистите, когда закончите, любые ссылки на классы, загруженные как часть развернутого webapp, останутся в постоянные кучи и никогда не будет собран "мусор". Перераспределения/отмена развертывания веб-приложения не будут убирать каждый Threadссылка на класс(ы) вашего webapp с Thread не является собственностью вашего webapp. Каждое последующее развертывание создаст новый экземпляр класса, который никогда не будет собирать мусор.

вы получите исключения из памяти из-за java.lang.OutOfMemoryError: PermGen space и после некоторого googling, вероятно, просто увеличится -XX:MaxPermSize вместо исправления ошибки.

если вы в конечном итоге испытываете эти проблемы, вы можете определить, какой поток и класс сохраняет эти ссылки, используя анализатор памяти Eclipse и/или Фрэнк Kieviet руководство и followup.

обновление: повторно обнаружено запись в блоге Алекса Вассера это помогло мне выследить некоторых ThreadLocal проблемы, которые у меня были.


многие фреймворки используют ThreadLocals для поддержания некоторого контекста, связанного с текущим потоком. Например, когда текущая транзакция хранится в ThreadLocal, вам не нужно передавать ее в качестве параметра через каждый вызов метода, если кому-то из стека нужен доступ к ней. Веб-приложения могут хранить информацию о текущем запросе и сеансе в ThreadLocal, чтобы приложение имело легкий доступ к ним. С Guice вы можете использовать ThreadLocals при реализации настраиваемые области для введенных объектов (Guice по умолчанию сервлет области скорее всего, используйте их также).

ThreadLocals - это один из видов глобальных переменных (хотя и немного менее злых, потому что они ограничены одним потоком), поэтому вы должны быть осторожны при их использовании, чтобы избежать нежелательных побочных эффектов и утечек памяти. Разработайте API-интерфейсы таким образом, чтобы значения ThreadLocal всегда автоматически очищались, когда они больше не нужны и что неправильно использование API будет невозможно (например,такой). ThreadLocals можно использовать для очистки кода, и в некоторых редких случаях они являются единственным способом заставить что-то работать (мой текущий проект имел два таких случая; они документированы здесь в разделе "статические поля и глобальные переменные").


в Java, если у вас есть datum, который может изменяться в потоке, ваш выбор-передать это datum каждому методу, который нуждается (или может понадобиться), или связать datum с потоком. Передача datum повсюду может быть работоспособной, если все ваши методы уже должны передавать общую переменную "контекста".

Если это не так, вы можете не захотеть загромождать свои подписи метода дополнительным параметром. В не-продетом нитку мире, вы смогли разрешить проблема с Java-эквивалентом глобальной переменной. В потоковом слове эквивалентом глобальной переменной является локальная переменная потока.


по существу, когда вам нужно значение переменной зависит от текущего потока и не удобно для вас прикреплять значение к потоку каким-либо другим способом (например, поток подклассов).

типичный случай, где некоторые другие рамки создали поток что ваш код выполняется, например, в контейнере сервлетов, или где имеет смысл использовать ThreadLocal, потому что ваша переменная тогда " в своей логике место " (а не переменная, зависающая от подкласса потока или в какой-либо другой хэш-карте).

на моем веб-сайте, у меня есть еще Обсуждение и примеры использования ThreadLocal это также может представлять интерес.

некоторые люди выступают за использование ThreadLocal как способ прикрепить "идентификатор потока" к каждому потоку в определенных параллельных алгоритмах, где вам нужен номер потока (см., например, Herlihy & Shavit). В таких случаях убедитесь, что вы действительно получаете выгода!


есть очень хороший пример в книге параллелизм Java на практике. Где автор (Джошуа Блох) объясняет, как ограничение потока является одним из самых простых способов достижения безопасности потока и ThreadLocal более формальных средств сохранения резьбы родов. В конце он также объясняет, как люди могут злоупотреблять им, используя его в качестве глобальных переменных.

Я скопировал текст из упомянутой книги, но код 3.10 отсутствует, поскольку это не так много важно понимать, где должен использоваться ThreadLocal.

локальные переменные потока часто используются для предотвращения совместного использования в конструкциях на основе изменяемых синглтоны и глобальные переменные. Например, однопоточное приложение может поддерживать глобальное соединение с базой данных, инициализированное при запуске, чтобы избежать необходимости передавать соединение каждому методу. Поскольку соединения JDBC не могут быть потокобезопасными, многопоточное приложение, которое использует глобальное соединение без дополнительных координация также не является потокобезопасной. Используя ThreadLocal для хранения соединения JDBC, как в ConnectionHolder в листинге 3.10, каждый поток будет иметь свое собственное соединение.

ThreadLocal широко используется в реализации платформы приложений. Например, контейнеры J2EE связывают контекст транзакции с выполняющимся потоком на время вызова EJB. Это легко реализуется с помощью статического локального потока, содержащего контекст транзакции: когда код платформы необходимо определить, какая транзакция в настоящее время выполняется, она извлекает контекст транзакции из этого ThreadLocal. Это удобно тем, что уменьшает необходимость передачи контекстной информации выполнения в каждый метод, но связывает любой код, который использует этот механизм, с платформой.

легко злоупотреблять ThreadLocal, рассматривая его свойство удержания потока как лицензию на использование глобальных переменных или как средство создания "скрытых" аргументов метода. Как глобальные переменные, локальные переменные потока могут отвлекать от повторного использования и вводить скрытые связи между классами и поэтому должны использоваться с осторожностью.


документация говорит это очень хорошо:"каждый поток, который обращается к [локальной переменной потока] (через его метод get или set), имеет свою собственную, независимо инициализированную копию переменной".

вы используете один, когда каждый поток должен иметь свою собственную копию чего-то. По умолчанию данные совместно используются потоками.


сервер Webapp может содержать пул потоков и ThreadLocal var должен быть удален до ответа клиенту, таким образом, текущий поток может быть повторно использован следующим запросом.


  1. ThreadLocal в Java был представлен на JDK 1.2, но позже был обобщен в JDK 1.5, чтобы ввести безопасность типов на ThreadLocal переменной.

  2. ThreadLocal может быть связан с областью потока, весь код, который выполняется потоком, имеет доступ к переменным ThreadLocal, но два потока не могут видеть друг друга переменную ThreadLocal.

  3. каждый поток содержит эксклюзивную копию переменной ThreadLocal, которая становится подходящей для сборки мусора после завершения потока или смерти, обычно или из-за любого исключения, учитывая, что ThreadLocal переменная не имеет никаких других живых ссылок.

  4. ThreadLocal переменные в Java, как правило, являются частными статическими полями в классах и поддерживают свое состояние внутри потока.

Подробнее: http://javarevisited.blogspot.com/2012/05/how-to-use-threadlocal-in-java-benefits.html#ixzz2XltgbHTK


два варианта использования, в которых можно использовать переменную threadlocal -
1-Когда у нас есть требование связать состояние с потоком (например, идентификатор пользователя или идентификатор транзакции). Это обычно происходит с веб-приложением, что каждый запрос, идущий на сервлет, имеет уникальный идентификатор транзакции, связанный с ним.

// This class will provide a thread local variable which
// will provide a unique ID for each thread
class ThreadId {
    // Atomic integer containing the next thread ID to be assigned
    private static final AtomicInteger nextId = new AtomicInteger(0);

    // Thread local variable containing each thread's ID
    private static final ThreadLocal<Integer> threadId =
        ThreadLocal.<Integer>withInitial(()-> {return nextId.getAndIncrement();});

    // Returns the current thread's unique ID, assigning it if necessary
    public static int get() {
        return threadId.get();
    }
}

обратите внимание, что здесь метод withInitial реализован с использованием лямбда-выражения.
2-Другой вариант использования-когда мы хотим иметь потокобезопасный экземпляр, и мы не хотите использовать синхронизацию, так как стоимость производительности с синхронизацией больше. Одним из таких случаев является использование SimpleDateFormat. Поскольку SimpleDateFormat не является потокобезопасным, поэтому мы должны предоставить механизм, чтобы сделать его потокобезопасным.

public class ThreadLocalDemo1 implements Runnable {
    // threadlocal variable is created
    private static final ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue(){
            System.out.println("Initializing SimpleDateFormat for - " + Thread.currentThread().getName() );
            return new SimpleDateFormat("dd/MM/yyyy");
        }
    };

    public static void main(String[] args) {
        ThreadLocalDemo1 td = new ThreadLocalDemo1();
        // Two threads are created
        Thread t1 = new Thread(td, "Thread-1");
        Thread t2 = new Thread(td, "Thread-2");
        t1.start();
        t2.start();
    }

    @Override
    public void run() {
        System.out.println("Thread run execution started for " + Thread.currentThread().getName());
        System.out.println("Date formatter pattern is  " + dateFormat.get().toPattern());
        System.out.println("Formatted date is " + dateFormat.get().format(new Date()));
    } 

}

вы должны быть очень осторожны с ThreadLocal рисунком. Есть некоторые основные нижние стороны, как упоминал Фил, но один, который не был упомянут,-это убедиться, что код, который устанавливает контекст ThreadLocal, не является "повторно входящим"."

плохие вещи могут произойти, когда код, который устанавливает информацию, запускается во второй или третий раз, потому что информация в вашем потоке может начать мутировать, когда вы этого не ожидали. Поэтому позаботьтесь о том, чтобы информация ThreadLocal не была установите, прежде чем установить его снова.


ничего нового здесь нет, но сегодня я обнаружил, что ThreadLocal очень полезно при использовании проверки Bean в веб-приложении. Сообщения проверки локализованы, но по умолчанию используются Locale.getDefault(). Вы можете настроить Validator С другой MessageInterpolator, но нет способа указать Locale когда вы называете validate. Таким образом, вы можете создать static ThreadLocal<Locale> (или еще лучше, общий контейнер с другими вещами, которые вам могут понадобиться ThreadLocal и тогда у вас есть свой обычай MessageInterpolator выбрать Locale от этого. Следующий шаг-написать ServletFilter который использует значение сеанса или request.getLocale() выбрать язык и сохранить его в ThreadLocal ссылка.


Как упоминалось @unknown (google), его использование заключается в определении глобальной переменной, в которой указанное значение может быть уникальным в каждом потоке. Это использование обычно влечет за собой хранение какой-то контекстной информации, связанной с текущим потоком выполнения.

мы используем его в среде Java EE для передачи идентификатора пользователя классам, которые не знают Java EE (не имеют доступа к HttpSession или EJB SessionContext). Таким образом, код, который делает использование identity для операций на основе безопасности, может получить доступ к идентификатору из любого места, без необходимости явно передавать его в каждом вызове метода.

цикл запроса/ответа операций в большинстве вызовов Java EE упрощает этот тип использования, поскольку он дает четко определенные точки входа и выхода для установки и отмены ThreadLocal.


ThreadLocal обеспечит доступ к изменяемому объекту несколькими потоки не синхронизированы метод synchronized, означает, что изменяемый объект должен быть неизменяемым внутри метода.

этот достигается путем предоставления нового экземпляра изменяемого объекта для каждого потока попробуйте получить к нему доступ. Так что локальная копия в каждой нити. Это взломать создание переменной экземпляра в методе, к которому можно получить доступ, как локальная переменная. Как вы знаете, метод local переменная доступна только для потока одно отличие: локальные переменные метода не будут доступный потоку после выполнения метода, где как изменяемый объект совместно с threadlocal будет доступен в нескольких методы пока мы убираем.

По Определению:

класс ThreadLocal в Java позволяет создавать переменные, которые могут только читать и писать одной нитью. Таким образом, даже если два нити выполняется тот же код, и код ссылку на ThreadLocal переменная, то два потока не могут видеть друг друга ThreadLocal переменные.

каждого Thread в java содержит ThreadLocalMap в нем.
Где

Key = One ThreadLocal object shared across threads.
value = Mutable object which has to be used synchronously, this will be instantiated for each thread.

достижение ThreadLocal:

Теперь создайте класс-оболочку для ThreadLocal, который будет содержать изменяемый объект, как показано ниже (С или без initialValue()).
Теперь геттер и сеттер этой оболочки будут работать на экземпляре threadlocal вместо изменяемого объекта.

если getter () threadlocal не нашел никакого значения в threadlocalmap Thread; затем он вызовет initialValue (), чтобы получить его частную копию относительно потока.

class SimpleDateFormatInstancePerThread {

    private static final ThreadLocal<SimpleDateFormat> dateFormatHolder = new ThreadLocal<SimpleDateFormat>() {

        @Override
        protected SimpleDateFormat initialValue() {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd") {
                UUID id = UUID.randomUUID();
                @Override
                public String toString() {
                    return id.toString();
                };
            };
            System.out.println("Creating SimpleDateFormat instance " + dateFormat +" for Thread : " + Thread.currentThread().getName());
            return dateFormat;
        }
    };

    /*
     * Every time there is a call for DateFormat, ThreadLocal will return calling
     * Thread's copy of SimpleDateFormat
     */
    public static DateFormat getDateFormatter() {
        return dateFormatHolder.get();
    }

    public static void cleanup() {
        dateFormatHolder.remove();
    }
}

теперь wrapper.getDateFormatter() будем называть threadlocal.get() и это проверит currentThread.threadLocalMap содержит этой (threadlocal) экземпляра.
Если да, возвращает значение (SimpleDateFormat) для соответствующего threadlocal экземпляра
в противном случае добавьте карту с этим экземпляром threadlocal, initialValue().

при этом безопасность потока достигается на этом изменяемом классе; каждым потоком работает со своим собственным изменяемым экземпляром, но с тем же экземпляром ThreadLocal. Означает, что весь поток будет использовать один и тот же экземпляр ThreadLocal как key, но другой экземпляр SimpleDateFormat как значение.

https://github.com/skanagavelu/yt.tech/blob/master/src/ThreadLocalTest.java


когда?

когда объект не является потокобезопасным, вместо синхронизации, которая препятствует масштабируемости, дайте один объект каждому потоку и сохраните его область потока, которая является ThreadLocal. Одним из наиболее часто используемых, но не потокобезопасных объектов являются database Connection и JMSConnection.

Как ?

одним из примеров является Spring framework использует ThreadLocal сильно для управления транзакциями за кулисами, сохраняя эти объекты соединения в ThreadLocal переменная. На высоком уровне при запуске транзакции она получает соединение (и отключает автоматическую фиксацию ) и сохраняет его в ThreadLocal. при дальнейших вызовах БД он использует то же соединение для связи с БД. В конце он берет соединение из ThreadLocal и фиксирует (или откат ) транзакцию и освобождает соединение.

Я думаю, что log4j также использует ThreadLocal для поддержания MDC.


ThreadLocal полезно, когда вы хотите иметь некоторое состояние, которое не должно совместно использоваться между различными потоками, но оно должно быть доступно из каждого потока в течение всего его срока службы.

в качестве примера, представим себе веб-приложение, где каждый запрос обслуживается в отдельном потоке. Представьте, что для каждого запроса нужен кусок данных несколько раз, что довольно дорого для вычисления. Однако эти данные могут изменяться для каждого входящего запроса, что означает, что вы не можете используйте простой кэш. Простым и быстрым решением этой проблемы было бы иметь ThreadLocal переменная, доступ к этим данным, так что вы должны рассчитать только один раз для каждого запроса. Конечно, эта проблема также может быть решена без использования ThreadLocal, но я разработал его для иллюстрации.

что сказал, имейте в виду, что ThreadLocals являются по существу формой глобального государства. В результате он имеет много других последствий и должен использоваться только после рассмотрения всех другие возможные решения.


локальные переменные потока часто используются для предотвращения совместного использования в конструкциях на основе изменяемые Синглеты или глобальные переменные.

его можно использовать в сценариях, таких как создание отдельного соединения JDBC для каждого потока, когда вы не используете пул соединений.

private static ThreadLocal<Connection> connectionHolder
           = new ThreadLocal<Connection>() {
      public Connection initialValue() {
           return DriverManager.getConnection(DB_URL);
          }
     };

public static Connection getConnection() {
      return connectionHolder.get();
} 

когда вы вызываете getConnection, он вернет соединение, связанное с этим потоком.То же самое можно сделать с другими свойствами, такими как dateformat, контекст транзакции, который вы не хотите делиться между нити.

вы также могли бы использовать локальные переменные для того же самого, но эти ресурсы обычно занимают время при создании,поэтому вы не хотите создавать их снова и снова, когда вы выполняете с ними некоторую бизнес-логику. Однако значения ThreadLocal хранятся в самом объекте thread, и как только поток собирается мусор, эти значения также исчезают.

этой ссылке объясняет использование ThreadLocal очень хорошо.


С момента выпуска Java 8 существует более декларативный способ инициализации ThreadLocal:

ThreadLocal<Cipher> local = ThreadLocal.withInitial(() -> "init value");

до выпуска Java 8 вам нужно было сделать следующее:

ThreadLocal<String> local = new ThreadLocal<String>(){
    @Override
    protected String initialValue() {
        return "init value";
    }
};

более того, если метод создания экземпляра (конструктор, заводской метод) класса, который используется для ThreadLocal не принимает никаких параметров, вы можете просто использовать ссылки на метод (введен в Java 8):

class NotThreadSafe {
    // no parameters
    public NotThreadSafe(){}
}

ThreadLocal<NotThreadSafe> container = ThreadLocal.withInitial(NotThreadSafe::new);

Примечание: Оценка ленива, так как вы проходите java.util.function.Supplier лямбда это оценивается только тогда, когда ThreadLocal#get вызывается, но значение ранее не оценивалось.


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


ThreadLocal-это специально подготовленная функциональность JVM для предоставления изолированного пространства хранения только для потоков. как и значение переменной области видимости экземпляра, привязаны только к данному экземпляру класса. каждый объект имеет свои значения и они не могут видеть друг друга значения. таково понятие ThreadLocal переменных, они являются локальными для потока в смысле экземпляров объектов другого потока, кроме того, который его создал, не может его видеть. посмотреть Вот!--3-->

import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;


public class ThreadId {
private static final AtomicInteger nextId = new AtomicInteger(1000);

// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> nextId.getAndIncrement());


// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
    return threadId.get();
}

public static void main(String[] args) {

    new Thread(() -> IntStream.range(1, 3).forEach(i -> {
        System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
    })).start();

    new Thread(() -> IntStream.range(1, 3).forEach(i -> {
        System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
    })).start();

    new Thread(() -> IntStream.range(1, 3).forEach(i -> {
        System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
    })).start();

}
}

Threadlocal обеспечивает очень простой способ для достижения повторного использования объектов с нулевой стоимостью.

У меня была ситуация, когда несколько потоков создавали образ изменяемого кэша при каждом уведомлении об обновлении.

Я использовал Threadlocal в каждом потоке, а затем каждому потоку просто нужно было сбросить старое изображение, а затем обновить его снова из кэша в каждом уведомлении об обновлении.

обычные многоразовые объекты из пулов объектов имеют стоимость безопасности потоков, связанную с их, пока такого подхода нет.


на ThreadLocal класс в Java позволяет создавать переменные, которые могут быть прочитаны и записаны только одним потоком. Таким образом, даже если два потока выполняют один и тот же код, и код имеет ссылку на переменную ThreadLocal, то два потока не могут видеть переменные ThreadLocal друг друга.

подробнее