Назначение ThreadLocal?

Назначение ThreadLocal, как указано здесь, гласит, что переменная является локальной для любого потока, обращающегося к объекту, содержащему переменную ThreadLocal. Какая разница, если иметь переменную ThreadLocal в качестве члена класса и затем делать ее локальной для потока, а не иметь локальную переменную для самого потока?

6 ответов

Решение

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

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

doGet(HttpServletRequest req, HttpServletResponse resp) {
  User user = getLoggedInUser(req);
  doSomething()
  doSomethingElse()
  renderResponse(resp)
}

Что произойдет, если методам doSomething() требуется доступ к объекту пользователя? Вы не можете сделать пользовательский объект экземпляром или статической переменной, потому что тогда каждый поток будет использовать один и тот же пользовательский объект. Вы можете передать пользовательский объект как параметр, но это быстро становится беспорядочным и приводит к утечке пользовательских объектов в каждый вызов метода:

doGet(HttpServletRequest req, HttpServletResponse resp) {
  User user = getLoggedInUser(req);
  doSomething(user)
  doSomethingElse(user)
  renderResponse(resp,user)
}

Более элегантное решение - поместить объект пользователя в ThreadLocal.

doGet(HttpServletRequest req, HttpServletResponse resp) {
  User user = getLoggedInUser(req);
  StaticClass.getThreadLocal().set(user)
  try {
    doSomething()
    doSomethingElse()
    renderResponse(resp)
  }
  finally {
    StaticClass.getThreadLocal().remove()
  }
}

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

User user = StaticClass.getThreadLocal().get()

Если вы используете этот подход, будьте внимательны, чтобы снова удалить объекты в блоке finally. В противном случае пользовательский объект может зависать в средах, использующих пул потоков (например, сервер приложений Tomcat).

Изменить: код для статического класса

class StaticClass {
  static private ThreadLocal threadLocal = new ThreadLocal<User>();

  static ThreadLocal<User> getThreadLocal() {
    return threadLocal;
  }
}

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

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

Конечно, вы можете попытаться сохранить приватность члена и убедиться, что он используется только run() или методы, вызываемые из него (публичные методы также могут вызываться из других потоков), но это подвержено ошибкам и неосуществимо для более сложной системы, где вы не хотите хранить все данные в подклассе Thread (на самом деле вы не предполагается создавать подкласс Thread, но вместо этого использовать Runnable).

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

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

Позвольте мне объяснить идею связывания результата с определенным потоком. Суть ThreadLocal примерно такая:

public class MyLocal<T> {
  private final Map<Thread, T> values = new HashMap<Thread, T>();

  public T get() {
    return values.get(Thread.currentThread());
  }

  public void set(T t) {
    values.put(Thread.currentThread(), t);
  }
}

Теперь это еще не все, но, как вы можете видеть, возвращаемое значение определяется текущим потоком. Вот почему это локально для каждого потока.

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

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

И не забывайте, что есть конструкции JAVA API, которые не являются потокобезопасными, например, DateFormat. Статический экземпляр DateFormat просто не работает на стороне сервера.

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

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

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

Другие вопросы по тегам