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

718

Когда следует использовать переменную ThreadLocal?

Как он используется?

  • 7
    Если вы используете ThreadLocal, никогда не пишите обертку поверх него !!! Каждый разработчик, который хочет использовать переменную, должен знать, что это «ThreadLocal»
  • 2
    @Notabug Если вы явно указали, вы можете назвать свою переменную так, чтобы она имела дело со значением ThreadLocal, например RequestUtils.getCurrentRequestThreadLocal() . Не сказать, что это очень элегантно, но это связано с тем, что сам ThreadLocal в большинстве случаев не очень элегантен.
Теги:
multithreading
concurrency
thread-local

19 ответов

759
Лучший ответ

Одно из возможных (и распространенных) использования - это когда у вас есть объект, который не является потокобезопасным, но вы хотите избежать синхронизации доступа к этот объект (я смотрю на вас, 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);
    }
}

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

  • 146
    Другая альтернатива синхронизации или threadlocal - сделать переменную локальной переменной. Местные переменные всегда безопасны для потоков. Я предполагаю, что это плохая практика - делать DateFormats локальными, потому что создавать их дорого, но я никогда не видел надежных показателей по этой теме.
  • 14
    Хороший способ начать локальную тему!
Показать ещё 14 комментариев
357

Так как a ThreadLocal - это ссылка на данные в заданном Thread, вы можете столкнуться с утечками в классе при использовании ThreadLocal на серверах приложений, которые используют пулы потоков. Вы должны быть очень осторожны при очистке любого ThreadLocal вас get() или set() с помощью метода ThreadLocal remove().

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

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

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

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

  • 1
    Как вы думаете, этого можно избежать с помощью SoftReference?
  • 10
    Алекс Вассер перенес свой блог. Вот текущая ссылка на статью об утечке памяти.
Показать ещё 7 комментариев
117

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

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

  • 3
    Именно так инфраструктура exPOJO (www.expojo.com) позволяет получить доступ к ORM Session / PersistenceManager, не требуя затрат на аннотации и внедрение. Это что-то вроде «инъекции потока» вместо «инъекции объекта». Он обеспечивает доступ к зависимостям без необходимости (и накладных расходов) их встраивания в каждый объект, который может нуждаться в этих зависимостях. Удивительно, как «легковес» вы можете сделать DI-фреймворк, когда вы используете инжекцию потока вместо классического DI (например, Spring и т. Д.)
  • 22
    Почему я должен прокрутить вниз, чтобы найти этот важный ответ !?
Показать ещё 3 комментария
39

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

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

  • 9
    Поэтому вы должны избегать локальных потоков так же, как вы избегаете глобальных. Я не могу согласиться с тем, что нормально создавать глобальные переменные (локальные потоки) вместо передачи значений, людям это не нравится, потому что это часто выявляет проблемы архитектуры, которые они не хотят исправлять.
  • 8
    Возможно ... Это может быть полезно, хотя, если у вас есть огромная, существующая кодовая база, к которой вы должны добавить новую опорную точку, которая должна передаваться везде , например, контекст сеанса, транзакция БД, зарегистрированный пользователь и т. Д. ,
Показать ещё 1 комментарий
12

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

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

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

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

10

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

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

  • 17
    По умолчанию статические объекты или объекты, явно передаваемые между потоками, где оба потока имеют ссылку на один и тот же объект, являются общими. Объекты, объявленные локально в потоке, не являются общими (они локальны для стека потока). Просто хотел уточнить это.
  • 0
    @IanVarley: Спасибо, что указали на это. Итак, если у нас есть переменная, которая объявлена и используется в классе MyThread, то нужен ли нам в этом случае ThreadLocal? Пример: класс MyThread extends Thread {String var1 ;, int var2; } в этом случае var1 и var2 будут частью собственного стека Thread и не будут использоваться совместно, поэтому требуется ли нам в этом случае ThreadLocal? Я могу быть совершенно не прав в моем понимании, пожалуйста, руководство.
Показать ещё 2 комментария
8

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

7

В книге Java Concurrency на практике имеется очень хороший пример. Там, где он объясняет, каким образом ограничение потока является одним из самых простых способов обеспечения безопасности потоков, а Thread Local является более формальным средством поддержания ограничения потока. В конце он также объясняет, как люди могут злоупотреблять им, используя его в качестве глобальных переменных.

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

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

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

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

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

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

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

  • Переменные ThreadLocal в Java обычно являются частными статическими полями в классах и поддерживают его состояние внутри Thread.

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

4

Два варианта использования, в которых можно использовать переменную 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 ID
    private static final ThreadLocal<Integer> threadId =
        ThreadLocal.<Integer>withInitial(()-> {return nextId.getAndIncrement();});

    // Returns the current thread 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()));
    } 

}
4

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

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

  • 2
    Повторный вход не является проблемой, если код готов справиться с этим. При вводе запишите, установлена ли переменная, и при выходе восстановите ее предыдущее значение (если оно было) или удалите его (если нет).
  • 1
    @ Джефф, Чувак, это верно для каждого кода, который ты пишешь, а не только для шаблона ThreadLocal . Если вы делаете F(){ member=random(); F2(); write(member); } и F2 переопределяет член новым значением, тогда очевидно, что write(member) больше не будет писать число, которое вы имели random() . Это буквально просто здравый смысл. Точно так же, если вы делаете F(){ F(); } , тогда удачи в вашем бесконечном цикле! Это верно везде и не относится к ThreadLocal .
3

когда?

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

Как?

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

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

3

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

Это достигается путем предоставления нового экземпляра изменяемого объекта для каждого потока попробуйте обратиться к нему. Так что это локальная копия каждого потока. Это некоторые взломать переменную экземпляра в методе, к которому нужно получить доступ, например, локальная переменная. Как вы знаете, локальная переменная метода доступна только к потоку, одно отличие; метод локальных переменных не будет доступный для потока после выполнения метода, где в качестве изменчивого объект, совместно используемый с 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()).
Теперь getter и setter этой оболочки будут работать с экземпляром 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 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 как ключ, но другой экземпляр SimpleDateFormat как значение.

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

3

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

3

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

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

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

2

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

Он может использоваться в сценариях, таких как создание отдельного соединения 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 сохраняются в самом объекте потока, и как только поток мусора собирается, эти значения также исчезают.

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

  • 0
    Обновлено с некоторым кодом и описанием.
  • 1
    В этом примере основная проблема: кто отвечает за закрытие соединения? Вместо того, чтобы создавать это соединение в качестве начального значения, позвольте потребителю соединения явно создать соединение и связать его с потоком через ThreadLocal. Создатель соединения также отвечает за закрытие. Это не ясно в этом примере. Создание и привязка соединения также могут быть скрыты в простой структуре с помощью чего-то вроде Transaction.begin () и Transaction.end ().
2

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

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

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

  • 0
    ThreadLocals НЕ являются глобальным состоянием, если вы не сделаете его глобальным состоянием. Они практически всегда доступны стековым ресурсом. Вы можете смоделировать поток локально, просто передав эту переменную во все ваши методы (что является запаздыванием); это не делает его глобальным государством ...
  • 0
    Это форма глобального состояния в том смысле, что она доступна из любого места кода (в контексте того же потока). Это происходит со всеми последствиями, такими как неспособность рассуждать о том, кто читает и записывает это значение. Использование параметра функции - не запаздывающая вещь, это хорошая практика продвижения чистых интерфейсов. Однако я согласен с вами, что передача параметра по всей глубине вашей кодовой базы - это запах кода. Но я также считаю, что во многих случаях использование ThreadLocal - это прежде всего запах кода, который привел вас сюда, так что это то, что нужно пересмотреть.
Показать ещё 2 комментария
0

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

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 ID
private static final ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> nextId.getAndIncrement());


// Returns the current thread 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();

}
}
0

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню